Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
246 commits
Select commit Hold shift + click to select a range
4265e89
feat(mcp): add AirtableClient.createRecords (internal-API row create)
ARHAEEM Jun 15, 2026
3e063d9
refactor(mcp): validate optional viewId + clarify error in createRecords
ARHAEEM Jun 15, 2026
8b4f1cd
feat(mcp): add AirtableClient.updateRecords + deleteRecords
ARHAEEM Jun 15, 2026
0a0e85d
fix(mcp): updateRecords per-row isolation + flag empty cell updates
ARHAEEM Jun 15, 2026
f901277
feat(mcp): add AirtableClient.uploadAttachment (5-step multipart)
ARHAEEM Jun 15, 2026
eaf149b
refactor(mcp): guard columnId + presignedUrl in uploadAttachment; ass…
ARHAEEM Jun 15, 2026
1834221
feat(mcp): expose create_records / update_records / delete_records tools
ARHAEEM Jun 15, 2026
52f1a87
refactor(mcp): correct update_records idempotency hint + clarify reco…
ARHAEEM Jun 15, 2026
3ac79e5
chore(tool-sync): register record-write tools across mirror + counts
ARHAEEM Jun 15, 2026
8d2b2b9
feat(mcp): gate record deletes behind new record-destructive category…
ARHAEEM Jun 16, 2026
66268f8
test(mcp): cover viewId fallback, id guards, and attachment soft-fail…
ARHAEEM Jun 16, 2026
eec14e8
feat(sync): add schema snapshot/normalize for plan engine
ARHAEEM Jun 16, 2026
71900d5
feat(sync): add idmap name-matching + sync-state persistence
ARHAEEM Jun 16, 2026
cf01096
feat(sync): add computed-field canonicalization for ref-stable diffing
ARHAEEM Jun 16, 2026
0f4ff23
chore(mcp): recurse test glob to include test/sync suites
ARHAEEM Jun 16, 2026
019e24c
feat(sync): add schema diff -> ordered plan (create/update/orphan, re…
ARHAEEM Jun 16, 2026
0a9444b
fix(sync): canonical typeOptions diff for computed fields + ordering …
ARHAEEM Jun 16, 2026
168ce62
feat(sync): add plan report + orchestrator with dest fingerprint
ARHAEEM Jun 16, 2026
679868b
feat(mcp): add sync_base tool (mode=plan) in new sync category
ARHAEEM Jun 16, 2026
b645f2c
chore(tool-sync): register sync category + sync_base across mirror + …
ARHAEEM Jun 16, 2026
0532e5b
feat(sync): capture link foreign-table deps + document source-id cont…
ARHAEEM Jun 16, 2026
a72b4ac
feat(sync): carry sourcePrimaryFieldId + field description in plan ac…
ARHAEEM Jun 16, 2026
9b2faa9
feat(sync): add remapRefs source->dest rewrite + writable computed op…
ARHAEEM Jun 16, 2026
dc141ef
test(sync): lock brace-aware remapRefs (string-literal safety)
ARHAEEM Jun 16, 2026
dad6371
feat(sync): add resumable apply journal + loadPlan
ARHAEEM Jun 16, 2026
e4b9388
test(sync): add deterministic stateful mock client for apply tests
ARHAEEM Jun 16, 2026
17686d0
feat(sync): apply skeleton -- createTable (scaffolding-delete) + prim…
ARHAEEM Jun 16, 2026
8e69e3f
feat(sync): apply scalar createField with existence-skip idempotency
ARHAEEM Jun 16, 2026
b64fec8
feat(sync): apply computed createField -- writable shape, validate, s…
ARHAEEM Jun 16, 2026
1d0dd90
feat(sync): apply foreignKey link createField with reciprocal-once ad…
ARHAEEM Jun 16, 2026
87fccdb
feat(sync): apply updateField -- safe changes, select-merge, retype d…
ARHAEEM Jun 16, 2026
a91a538
test(sync): prove apply journal-resume + run-twice idempotency
ARHAEEM Jun 16, 2026
f3f364c
feat(sync): apply orchestrator with drift guard + journal/idmap persi…
ARHAEEM Jun 16, 2026
384b0e6
feat(mcp): sync_base mode=apply (execute saved plan, drift-guarded)
ARHAEEM Jun 16, 2026
f9e666a
docs(sync): document sync_base mode=apply across CLAUDE.md, CHANGELOG…
ARHAEEM Jun 16, 2026
a6ba2a4
fix(sync): skip computed field with unresolvable refs instead of halt…
ARHAEEM Jun 16, 2026
4fac816
fix(sync): apply updateField computed writable shape + continue-on-fa…
ARHAEEM Jun 16, 2026
cc7e7d9
fix(sync): converge plan — select choices are superset-equal, skip em…
ARHAEEM Jun 16, 2026
0c3ed54
feat(sync): add remapViewConfig + canonicalizeViewConfig for view sync
ARHAEEM Jun 16, 2026
e9bd5b2
feat(sync): snapshot collaborative views + live config (sortSet unwra…
ARHAEEM Jun 16, 2026
2596d41
feat(sync): match collaborative views by name in idmap
ARHAEEM Jun 16, 2026
c420b3b
feat(sync): diff emits createView + applyViewConfig (convergent, afte…
ARHAEEM Jun 16, 2026
9f2b909
feat(sync): apply createView (adopt-by-name) + mock view state
ARHAEEM Jun 16, 2026
9a85799
fix(sync): createTable index entry includes viewsByName so same-run c…
ARHAEEM Jun 16, 2026
4d17b2d
feat(sync): apply applyViewConfig -- facets, anchor validation (kanba…
ARHAEEM Jun 16, 2026
a5bbc73
feat(sync): view-aware fingerprint + count view actions in plan report
ARHAEEM Jun 16, 2026
ba4179a
docs(sync): document collaborative view sync in sync_base
ARHAEEM Jun 16, 2026
814e61a
fix(client): setViewColumns verify-readback + retry (bulk under-apply…
ARHAEEM Jun 16, 2026
f434620
fix(sync): converge view columns -- canonical compares visible order …
ARHAEEM Jun 17, 2026
fe9b032
fix(sync): converge view columns on visible/hidden SET, not order (or…
ARHAEEM Jun 17, 2026
a0172af
fix(sync): exclude record-based colorConfig (colorDefinitions) from v…
ARHAEEM Jun 17, 2026
5295e49
fix(sync): clear stray dest view facets source lacks (filters/sorts/g…
ARHAEEM Jun 17, 2026
c96e192
fix(sync): strip+report record-referencing view filters (no dangling …
ARHAEEM Jun 17, 2026
7bb4974
feat(sync): record snapshot via queryRecords summary.rows
ARHAEEM Jun 17, 2026
7977d00
feat(sync): persist records map in idmap
ARHAEEM Jun 17, 2026
695e041
feat(sync): cells.js pass-1 scalar/select coercion + writable gate
ARHAEEM Jun 17, 2026
9d75144
feat(sync): cells.js link partition (resolved/unresolved)
ARHAEEM Jun 17, 2026
a0f14fa
feat(sync): ratelimit token-bucket + transient retry
ARHAEEM Jun 17, 2026
0ec4768
feat(sync): records journal file (distinct from schema journal)
ARHAEEM Jun 17, 2026
d1d9f2b
fix(test): expand records-journal test — distinct filename, env clean…
ARHAEEM Jun 17, 2026
3283b80
feat(sync): records Pass 1 (scalar/select upsert + fill record map)
ARHAEEM Jun 17, 2026
90009f4
feat(sync): records Pass 2 link cells via addLinkItems (idempotent)
ARHAEEM Jun 17, 2026
0290dc0
fix(sync): harden link-cell shape handling in records Pass 1 & 2
ARHAEEM Jun 17, 2026
02ce8f1
feat(sync): record attachments (download/upload/verify/dedupe, non-bl…
ARHAEEM Jun 17, 2026
ecab2db
feat(sync): reapplyViewFilters — remap record-ref filters once record…
ARHAEEM Jun 17, 2026
950db47
feat(sync): wire records phase into apply + reconcile mode + pre-flight
ARHAEEM Jun 17, 2026
96d21f9
feat(sync): sync_base mode=reconcile; apply now syncs records; record…
ARHAEEM Jun 17, 2026
b7d5788
fix(auth): bound session init/recovery with a timeout (records apply …
ARHAEEM Jun 17, 2026
78a254d
perf(sync): records phase uses schema-only snapshot (eliminate getVie…
ARHAEEM Jun 17, 2026
9a46f27
feat(sync): run the records phase as a background job (non-blocking a…
ARHAEEM Jun 17, 2026
0038d4a
docs(sync): document M3-records (background record sync, mode=status,…
ARHAEEM Jun 17, 2026
6c191e1
fix(sync): final-review fixes — preserve records map across applies (…
ARHAEEM Jun 17, 2026
88a0c19
feat(sync): server-side cross-base attachment copy (fast-path)
ARHAEEM Jun 17, 2026
8a55c9d
fix(auth): circuit-breaker for a dead session (stop the 403→recover→4…
ARHAEEM Jun 17, 2026
9dca92b
fix(auth): back off on 403/429 (don't relaunch) — stop amplifying the…
ARHAEEM Jun 18, 2026
44e5248
feat(sync): fold resolvable links into row/create (kills most of Pass 2)
ARHAEEM Jun 18, 2026
cd15864
feat(sync): proactively pace the records phase under ~5 req/s
ARHAEEM Jun 18, 2026
8c81b55
fix(sync): pace records POSTs via a phase-local gate, not the shared …
ARHAEEM Jun 18, 2026
8934a05
feat(sync): AIRTABLE_SYNC_RPS knob for the records-phase limiter
ARHAEEM Jun 18, 2026
dce82e2
feat(sync): capture view sections in normalizeSchema (no extra API call)
ARHAEEM Jun 19, 2026
1111950
feat(sync): compare.js classification map
ARHAEEM Jun 19, 2026
0275d26
feat(sync): compareFields with drift + best-effort field order
ARHAEEM Jun 19, 2026
18f3647
fix(sync): compareFields — guard choices on type-match; track matched…
ARHAEEM Jun 19, 2026
63ea942
feat(sync): compareTable/Views incl. sections + order
ARHAEEM Jun 19, 2026
77ae232
test(sync): pin frozen/rowHeight DiffEntry contract; document sort-or…
ARHAEEM Jun 19, 2026
ec22fe4
feat(sync): top-level compare() + identical/converged verdicts
ARHAEEM Jun 19, 2026
e69f2d3
fix(sync): converged must account for onlyInSource (missing) items, n…
ARHAEEM Jun 19, 2026
837f6a8
feat(sync): saveDiff/loadDiff/latestDiffId
ARHAEEM Jun 19, 2026
c768a67
feat(sync): renderDiff digest + drill-in
ARHAEEM Jun 19, 2026
96a6b35
feat(sync): add diff() + diffDetail() engine functions
ARHAEEM Jun 19, 2026
15559b0
feat(sync): sync_base mode=diff (digest + detail drill-in)
ARHAEEM Jun 19, 2026
4b6edf2
feat(sync): stable changeId + class + apply flag on plan actions
ARHAEEM Jun 19, 2026
bd0cab5
feat(sync): changeset digest + direction in plan()
ARHAEEM Jun 19, 2026
997ab90
feat(sync): journal-safe selective apply (skip-list + apply:false)
ARHAEEM Jun 19, 2026
c7098fc
feat(sync): thread skip-list through apply() + sync_base
ARHAEEM Jun 19, 2026
06f970a
docs(sync): compare/changeset/selective-apply
ARHAEEM Jun 19, 2026
b05f0ac
chore(sync): drop dead import; document to-source apply base-swap
ARHAEEM Jun 19, 2026
7c96c75
fix(sync): compare canonicalizes cross-base computed/link refs (no fa…
ARHAEEM Jun 19, 2026
7754291
fix(sync): strip read-only maxUsedAutoNumber so autoNumber fields create
ARHAEEM Jun 19, 2026
3fe3de5
fix(sync): count fields emit internal relationColumnId (not public re…
ARHAEEM Jun 19, 2026
818ba6c
docs(sync): changelog — autoNumber + count field-create fixes
ARHAEEM Jun 19, 2026
c871bb4
fix(sync): canonicalize empty-predicate filter operators (no false vi…
ARHAEEM Jun 19, 2026
c52118e
fix(sync): delete createTable scaffolding rows for a clean mirror
ARHAEEM Jun 20, 2026
0360193
docs(sync): changelog — createTable scaffolding-row cleanup
ARHAEEM Jun 20, 2026
5790df3
feat(sync): policy presets + resolvePolicy + isDeleting
ARHAEEM Jun 20, 2026
ddf069a
feat(sync): validateFieldMappings (fail-fast, 6 codes) + resolve to f…
ARHAEEM Jun 20, 2026
7cf8eeb
fix(sync): add multipleSelects to ARRAY_SOURCE_TYPES hardening
ARHAEEM Jun 20, 2026
02a0321
feat(sync): coerceMappedValue — scalar source->dest injection
ARHAEEM Jun 20, 2026
6e50a8f
feat(sync): Pass1 honors conflict policy (dest-wins preserves dest ed…
ARHAEEM Jun 20, 2026
f57539c
feat(sync): Pass1 injects resolved field mappings (source value -> de…
ARHAEEM Jun 20, 2026
dd20332
feat(sync): pruneRecords — delete dest-only orphans under mirror (gated)
ARHAEEM Jun 20, 2026
a87c337
fix(sync): result accumulator guards in pruneRecords
ARHAEEM Jun 20, 2026
bb54df6
feat(sync): runRecords core — pre-flight mapping validation + prune; …
ARHAEEM Jun 20, 2026
e046ad4
fix(sync): surface deleted in records job-status + relocate applyReco…
ARHAEEM Jun 20, 2026
af8a7f1
feat(sync): sync_base policy/fieldMappings params + plan-mode mapping…
ARHAEEM Jun 20, 2026
450cef6
fix(sync): synchronous fieldMappings pre-flight in apply() + dead DEL…
ARHAEEM Jun 20, 2026
0fefde6
docs(sync): document reconciliation policy + custom field mappings
ARHAEEM Jun 20, 2026
03ed538
fix(docs): clarify createTable scaffolding-row clearing is unconditional
ARHAEEM Jun 20, 2026
ab1fd2f
fix(sync): pruneRecords — snapshot-derived orphan detection, M3 scopi…
ARHAEEM Jun 20, 2026
772df21
chore: WIP auth/daemon/webview changes (separated from records-policy)
ARHAEEM Jun 20, 2026
80aa327
feat(sync): matchByNaturalKeys — add-only, ambiguity-safe record matc…
ARHAEEM Jun 21, 2026
0ff6d60
feat(sync): reconcile performs natural-key matching (removes stub)
ARHAEEM Jun 21, 2026
0d1e26b
feat(sync): runRecords natural-key pre-pass + thread naturalKeys thro…
ARHAEEM Jun 21, 2026
fa8a57e
feat(sync): sync_base passes naturalKeys to apply + surfaces matched …
ARHAEEM Jun 21, 2026
3f912d5
docs(sync): document natural-key record matching
ARHAEEM Jun 21, 2026
3418339
docs: fix accuracy (reconcile natural-key stale text + add naturalKey…
ARHAEEM Jun 21, 2026
059e1cb
docs: fix stale comment on reconcile natural-key matching
ARHAEEM Jun 21, 2026
6c433cf
feat(sync): pruneSchema — delete dest-only fields/views/tables under …
ARHAEEM Jun 21, 2026
e4e3fcc
feat(sync): apply() runs pruneSchema after applyPlan (threads confirm…
ARHAEEM Jun 21, 2026
0606069
feat(sync): sync_base confirmTableDeletions param + surface schema-de…
ARHAEEM Jun 21, 2026
bc29bd8
docs(sync): document schema-orphan deletion under mirror
ARHAEEM Jun 21, 2026
87b1e05
docs(changelog): fix stale deferred-follow-ups sentence
ARHAEEM Jun 21, 2026
40d5643
fix(sync): guard pruneSchema with aborted check
ARHAEEM Jun 21, 2026
5e5860f
feat(sync): apply scalar field retypes under confirmRetypes (was RETY…
ARHAEEM Jun 21, 2026
2f3c500
feat(sync): sync_base confirmRetypes param + surface retyped count
ARHAEEM Jun 21, 2026
3c0fe98
docs(sync): document scalar field retypes under confirmRetypes
ARHAEEM Jun 21, 2026
2d9e397
fix(sync): pruneRecords skips truncated tables (>1000 rows) to preven…
ARHAEEM Jun 21, 2026
505125f
feat(sync): runRecords flags truncated tables + warns; thread guard i…
ARHAEEM Jun 21, 2026
27d4682
feat(sync): capture view-section id in snapshot; compare sections by …
ARHAEEM Jun 21, 2026
81d59a1
feat(sync): delete dest-only view-section orphans under mirror (prune…
ARHAEEM Jun 21, 2026
a34996e
docs(sync): prune-schema header comment lists sections + deletion order
ARHAEEM Jun 21, 2026
70f308d
fix(security): stop logging CSRF token prefix; drop daemon port from …
ARHAEEM Jun 22, 2026
7ea117f
fix(sync): recognize INTERNAL array type spellings in cells + fieldMa…
ARHAEEM Jul 4, 2026
50bfe7b
fix(sync): preserve persisted idmap.records/attachments across plan/diff
ARHAEEM Jul 4, 2026
fbaefab
fix(sync): defer ALL array-shaped cells on the Pass-1 update path
ARHAEEM Jul 4, 2026
24b9b0f
fix(sync): guard mode=apply with a per-pair lock file
ARHAEEM Jul 4, 2026
ac370e0
fix(sync): use escaped separator in arrayCellEquals (drop raw NUL byte)
ARHAEEM Jul 4, 2026
fc00f3d
fix(sync): make journal resume reachable past the drift guard
ARHAEEM Jul 4, 2026
3537158
fix(sync): propagate cells cleared on source as explicit nulls (sourc…
ARHAEEM Jul 4, 2026
385547c
fix(sync): harden apply engine — exact reverse-link adoption, retryab…
ARHAEEM Jul 4, 2026
a7b9d27
fix(sync): skip update rows with an empty cell map
ARHAEEM Jul 4, 2026
571f002
fix(sync): compare matched link fields canonically — no phantom updat…
ARHAEEM Jul 4, 2026
1205974
fix(sync): compare writable display-format options on computed fields
ARHAEEM Jul 4, 2026
dd9a0aa
fix(sync): pass collaborator usr ids through view-filter remap verbatim
ARHAEEM Jul 4, 2026
8d34ab3
fix(sync): match views by (name, type) — cross-type same-name views a…
ARHAEEM Jul 4, 2026
79ba83d
fix(sync): never emit the dest primary field as a deletable orphan
ARHAEEM Jul 4, 2026
f2cce19
fix(sync): guard reconcile existence-prune against truncated dest sna…
ARHAEEM Jul 4, 2026
cef92c5
fix(sync): truncation detection counts fetched rows, not folded synth…
ARHAEEM Jul 4, 2026
81009ad
fix(sync): mirror prune propagates source record deletions (stale idm…
ARHAEEM Jul 4, 2026
f98710c
fix(sync): Pass 2 links + Pass 3 attachments honor conflicts=dest-wins
ARHAEEM Jul 4, 2026
3004f7d
fix(sync): scope attachment-fallback dedupe key to the destination cell
ARHAEEM Jul 4, 2026
bcf2c72
fix(sync): filter restore survives per-view getView failures; never b…
ARHAEEM Jul 4, 2026
fb15ffc
fix(sync): record snapshots avoid filtered views; partial reads block…
ARHAEEM Jul 4, 2026
b08f1ea
fix(sync): mode=status detects a records job whose process died mid-run
ARHAEEM Jul 4, 2026
8d80a98
fix(sync): createField registers the real select choice map for the s…
ARHAEEM Jul 4, 2026
de9f9b5
fix(sync): records seams — computed-dest clear skip, resume-durable c…
ARHAEEM Jul 4, 2026
b6fa5f9
fix(sync): apply-engine seams — (name,type) view adoption, deferred c…
ARHAEEM Jul 4, 2026
c98f5ab
docs: update tool count to 71, document upload_attachment and daemon …
ARHAEEM Jul 4, 2026
fe5807b
feat(transport): route Airtable API calls through direct node HTTP
ARHAEEM Jul 4, 2026
91e22a5
test(transport): cover HttpTransport + auth direct-HTTP seam
ARHAEEM Jul 4, 2026
9af6601
refactor(auth): drop blocking secretSocketId nav-capture (live-proven…
ARHAEEM Jul 4, 2026
da34bd1
feat(auth): AIRTABLE_AUTH_MODE=byo — cookie-only credentials, no browser
ARHAEEM Jul 4, 2026
36e977e
feat(auth): AIRTABLE_AUTH_MODE=direct-login — browser-free login via …
ARHAEEM Jul 4, 2026
3921af7
fix(auth): recover from a locked Chrome profile (exit 21) by killing …
ARHAEEM Jul 4, 2026
eefd80e
feat(sync): run plan + apply as background jobs (mode=status phases)
ARHAEEM Jul 4, 2026
d56f556
docs: document transport modes (direct-HTTP + auth-mode + http-client…
ARHAEEM Jul 4, 2026
3ea20d2
fix(sync): harden applyJob terminal write + document apply() throw co…
ARHAEEM Jul 4, 2026
2803eac
feat(mcp): re-provision stdio MCP servers when authMode/httpClient ch…
ARHAEEM Jul 4, 2026
0e0d0e4
feat(daemon): inject byo/direct-login credentials via a bearer-authed…
ARHAEEM Jul 4, 2026
8abaecf
feat(auth): byo + direct-login credential handling in extension (keyc…
ARHAEEM Jul 7, 2026
2d04354
fix(formula): resolve field names in downloads + normalize legacy ref…
ARHAEEM Jul 9, 2026
d3204b1
feat(mcp): idle park Chromium, PageScheduler, process-tree, tool labels
ARHAEEM Jul 22, 2026
ae481e1
fix(mcp): share auth for pageBusy; update multiSelect on records
ARHAEEM Jul 22, 2026
ace1e42
fix(mcp): address PR #19 review — CI lockfile, auth/daemon lifecycle,…
ARHAEEM Jul 22, 2026
cf4e987
fix(build): keep impit external in the MCP bundle + vendor it for pac…
ARHAEEM Jul 22, 2026
da0c5b4
test(mcp): make process-tree + idmap tests deterministic across OSes
ARHAEEM Jul 22, 2026
c0a9010
fix(auth): single-flight _doInit so a watchdog timeout can't double-l…
ARHAEEM Jul 22, 2026
8673e04
fix(daemon): fail clearly if listenAvoidingBlockedPorts exhausts all …
ARHAEEM Jul 22, 2026
352b29c
fix(mcp): address PR #19 review — dead code, test engines, tracer red…
ARHAEEM Jul 24, 2026
35b389a
fix(extension): propagate transport-setting edits from settings.json …
ARHAEEM Jul 24, 2026
905ed73
docs(tooling): fix CLAUDE.md tool/category counts and guard them in c…
ARHAEEM Jul 24, 2026
e473cdb
fix(mcp): remove sync from safe-write; stop custom profiles gaining n…
ARHAEEM Jul 24, 2026
0e51c20
fix(mcp): correct toggle notifications for absent-key tools; make new…
ARHAEEM Jul 24, 2026
6634c24
fix(mcp): mark sync_base destructive; retire "read-only plan mode" wo…
ARHAEEM Jul 24, 2026
f677127
fix(extension): scope the orphan sweep to processes this install prov…
ARHAEEM Jul 24, 2026
39f4c55
fix(extension): require the daemon-start shape for path-based sweep o…
ARHAEEM Jul 24, 2026
032f01f
docs: fix stale 66-tool counts and sync scope claims in published REA…
ARHAEEM Jul 24, 2026
4545a4a
docs(assets): fix stale 66-tool/13-category text baked into banner/ar…
ARHAEEM Jul 24, 2026
e1675b6
fix(mcp-server): sanitize real account identifiers in sync test fixture
ARHAEEM Jul 24, 2026
ff8dba9
fix(mcp-server): give sync fixture's avatar/attachment hashes distinc…
ARHAEEM Jul 24, 2026
e6924b6
test(direct-login): pin CSRF reuse across 2FA — Seer stale-token find…
ARHAEEM Jul 24, 2026
3b71a39
fix(build): vendor impit's per-platform napi binary into the VSIX
ARHAEEM Jul 24, 2026
e88b3f7
fix(build): generalize the napi-binary vendoring fix to @ngrok/ngrok …
ARHAEEM Jul 24, 2026
0ca34c7
fix(auth): clear the dead-session breaker on re-authentication (8b)
ARHAEEM Jul 25, 2026
9c319f5
fix(extension): make every logout path actually drop the daemon sessi…
ARHAEEM Jul 25, 2026
b89963d
fix(mcp-server): honour HTTP(S)_PROXY/NO_PROXY and unwrap "fetch fail…
ARHAEEM Jul 25, 2026
28cb407
fix(auth): only un-latch the breaker, never zero a partial streak (8b…
ARHAEEM Jul 25, 2026
9fea048
fix(extension): logout must not report success it cannot confirm (8c/F1)
ARHAEEM Jul 25, 2026
8f85a77
fix(extension): a profile left on disk is not a completed logout (8c/F3)
ARHAEEM Jul 25, 2026
ba745ab
docs(changelog): document pre-merge pass scope + retroactively docume…
ARHAEEM Jul 25, 2026
0d67323
fix(mcp-server): correct serverInfo.description tool count and guard it
ARHAEEM Jul 25, 2026
58e5efd
fix(extension): logout must not let a vacuous release mask a failed drop
ARHAEEM Jul 25, 2026
eb87f60
fix(extension): fail closed on the recordDestructive/sync setting fal…
ARHAEEM Jul 25, 2026
853179e
fix(mcp-server): match getView column-visibility semantics; inject CS…
ARHAEEM Jul 25, 2026
1bb90dd
feat(packaging): define the VSIX target matrix and vendor foreign nat…
ARHAEEM Jul 25, 2026
68e8bf5
feat(packaging): assert every VSIX carries its own platform's binarie…
ARHAEEM Jul 25, 2026
634286b
feat(packaging): build one verified VSIX per target; drop untargeted …
ARHAEEM Jul 25, 2026
462886c
ci(release): publish 8 platform-specific VSIXes, no untargeted fallba…
ARHAEEM Jul 25, 2026
cf92b97
docs: correct the "fully self-contained" VSIX claim; document the pla…
ARHAEEM Jul 25, 2026
d016d0b
ci(release): bound the Open VSX index-verification wait across 8 targets
ARHAEEM Jul 25, 2026
455bfda
fix(mcp-server): guard CSRF spread against non-plain bodies; unify co…
ARHAEEM Jul 25, 2026
6e31e66
fix(packaging): verify native binaries by magic number, not just by n…
ARHAEEM Jul 25, 2026
bb0a636
ci(release): bound Open VSX verification by wall clock; fix the build…
ARHAEEM Jul 25, 2026
67999ce
fix(packaging): report vendor failures without an unhandled-rejection…
ARHAEEM Jul 25, 2026
119c9ea
docs: narrow tool-profile allowlist claim; document column-visibility…
ARHAEEM Jul 25, 2026
be862c3
fix(packaging): verify native binaries by exact digest, not by OS mag…
ARHAEEM Jul 25, 2026
5d1538d
fix(packaging): revalidate the vendor cache and guard foreign symlink…
ARHAEEM Jul 25, 2026
96daeed
docs: call these eight target artifact packaging/assertion smokes (18d)
ARHAEEM Jul 25, 2026
e8ee2fc
fix(extension): the orphan sweep must not kill by profile-path mentio…
ARHAEEM Jul 25, 2026
becd274
fix(packaging): reject unpinned .node files in the artifact, not just…
ARHAEEM Jul 25, 2026
5dd032b
fix(packaging): diff pins not prose; guard the generator's extraction…
ARHAEEM Jul 25, 2026
2e5c702
ci: verify native-binary digest pins at release and on dependency PRs
ARHAEEM Jul 25, 2026
a5c80ce
ci: widen the pin-check trigger; record that the PR job is advisory, …
ARHAEEM Jul 25, 2026
ec11ec6
fix(extension): make the sweep's image test actually mean argv[0] (17…
ARHAEEM Jul 25, 2026
26cfa0f
fix(mcp-server): only kill positively-attributable browsers on POSIX …
ARHAEEM Jul 25, 2026
2d77844
fix(mcp-server): replace too-loose plain-object test in the CSRF spre…
ARHAEEM Jul 25, 2026
05b54aa
fix(extension): resolve argv[0] by enumeration, not by patched exampl…
ARHAEEM Jul 25, 2026
427a3c7
fix(mcp-server): unify process attribution behind one shared, test-en…
ARHAEEM Jul 25, 2026
40a61a7
fix(mcp-server): pin the win32 PowerShell invocation to the reference…
ARHAEEM Jul 25, 2026
18c8614
fix(attribution): make the argv boundary symmetric and the bundle mar…
ARHAEEM Jul 25, 2026
fbd9487
fix(attribution): drop the `=` widening and give the quoted branch th…
ARHAEEM Jul 25, 2026
05917bd
fix(build): parameterize the symlink policy so verify-pins survives n…
ARHAEEM Jul 25, 2026
fb11377
fix(attribution): parse argv instead of matching quoted substrings (23)
ARHAEEM Jul 25, 2026
fe12bb2
docs: specify native Airtable workbench redesign
ARHAEEM Jul 25, 2026
0b444db
fix(sync): sort fields before hashing in fingerprintSchema (27)
ARHAEEM Jul 25, 2026
0ecff7f
docs: plan native Airtable workbench implementation
ARHAEEM Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 124 additions & 30 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ jobs:
# scope, so a compromised latest release must not be auto-pulled here.
- run: pnpm install -wD @vscode/vsce@3.9.2 ovsx@1.0.1

# Re-derive every native-binary digest from the registry and diff it
# against the committed pin file, BEFORE anything is packaged.
#
# What this uniquely catches: a pin file that is internally consistent —
# correct version, correct integrity — but carries WRONG DIGESTS. The two
# neighbouring failure modes are already covered offline and would never
# reach this step: a stale or missing pin fails closed in `binaryPin()`
# during vendoring, and a re-published tarball fails the lockfile SHA-512
# on download. A poisoned pin file is the residual trust root, and it is
# the one thing only a fresh re-derivation can expose.
#
# Placed here deliberately: the first publish is far below and the version
# bump/commit/tag lower still, so a failure here ships nothing and tags
# nothing. Kept OUT of `pnpm test` — it needs the network and pulls ~150 MB.
#
# THIS STEP IS THE GATE. `.github/workflows/verify-binary-pins.yml` runs
# the same check on pull requests, but it is path-filtered and therefore
# SKIPPED on most PRs — and a skipped run is not a verification. That
# workflow is early warning for the contributor; this step is the safety
# boundary. Do not remove it on the grounds that the PR job covers it.
- name: Verify native-binary digest pins
if: inputs.target == 'extension' || inputs.target == 'both'
run: pnpm check:binary-digests

# ── Compute versions ───────────────────────────────────────
# For each target, we check both package.json AND the registry.
# We bump from whichever is higher, so we never collide with
Expand Down Expand Up @@ -218,14 +242,25 @@ jobs:
- name: Run tests
run: pnpm test

# ── Package extension VSIX ─────────────────────────────────
- name: Package VSIX
# ── Package extension VSIX (one per platform target) ───────
#
# `impit` and `@ngrok/ngrok` keep their compiled .node binary in separate
# per-platform npm packages, so a single untargeted VSIX built on this
# ubuntu runner would ship Linux-x64 binaries to every user and break
# `mcp.httpClient: "impit"` and the ngrok tunnel everywhere else. We build
# one VSIX per target instead, each vendoring only its own platform
# packages (pinned to pnpm-lock.yaml by version AND integrity), and assert
# the contents of every artifact before anything can be published.
# The target matrix lives in scripts/vsix-targets.mjs.
#
# This runs on ONE runner rather than a job matrix: the foreign binaries
# are fetched as plain tarballs (nothing is compiled), which avoids
# needing arm64/alpine runners and keeps the version-bump → publish →
# commit → tag sequence below in a single, ordered job.
- name: Package platform-specific VSIXes
id: vsix
if: inputs.target == 'extension' || inputs.target == 'both'
run: |
# Prepare deps for VSIX
node scripts/prepare-package-deps.mjs

# Copy README: strip SVGs + collapse <picture> to single <img> for VS Code Marketplace
node -e "
const fs = require('fs');
Expand All @@ -245,19 +280,45 @@ jobs:
fs.writeFileSync('packages/extension/README.md', readme);
"

# Package
cd packages/extension
pnpm exec vsce package --no-dependencies
VSIX=$(ls *.vsix | head -1)
echo "file=packages/extension/${VSIX}" >> $GITHUB_OUTPUT
echo "Packaged: ${VSIX}"
# Eight target artifact packaging/assertion smokes: build one VSIX per
# published target and verify the CONTENTS of each — every .node
# matched byte-for-byte against scripts/native-binary-digests.json,
# which was recorded from tarballs verified against pnpm-lock.yaml.
# package-targets.mjs aborts on the first target that fails, so a bad
# artifact can never reach a publish step.
#
# These are packaging smokes, not runtime smokes. A single runner can
# only ever `require()` the binding built for itself, so only the host
# target's binding could receive a genuine runtime load; the other
# seven are verified by exact content, which is what the digests are
# for. Nothing here should be described as runtime-loading eight
# native bindings.
node scripts/package-targets.mjs --out-dir=artifacts

# Re-assert the finished set: every published target present, no
# untargeted artifact, right binaries — byte-exact — in each.
node scripts/assert-vsix-binaries.mjs --dir=artifacts

ls -la artifacts/

# ── Publish extension ──────────────────────────────────────
# Every artifact is published; there is deliberately NO untargeted
# fallback. A user on a platform we do not build for sees the extension
# as unavailable rather than installing one with the wrong binaries.
- name: Publish extension to VS Code Marketplace
if: |
!inputs.dry_run &&
(inputs.target == 'extension' || inputs.target == 'both')
run: pnpm exec vsce publish --packagePath "${{ steps.vsix.outputs.file }}"
run: |
ARGS=()
COUNT=0
for f in artifacts/*.vsix; do
# Two argv elements per file, so ${#ARGS[@]} is not the build count.
ARGS+=(--packagePath "$f")
COUNT=$((COUNT + 1))
done
echo "Publishing ${COUNT} platform builds to the Marketplace..."
pnpm exec vsce publish "${ARGS[@]}"
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}

Expand All @@ -266,26 +327,51 @@ jobs:
!inputs.dry_run &&
(inputs.target == 'extension' || inputs.target == 'both')
run: |
pnpm exec ovsx publish "${{ steps.vsix.outputs.file }}" --pat "$OVSX_PAT"
for f in artifacts/*.vsix; do
echo "Publishing $(basename "$f") to Open VSX..."
pnpm exec ovsx publish "$f" --pat "$OVSX_PAT"
done

# Verify the version actually landed (Open VSX indexes asynchronously)
# Verify each target actually landed (Open VSX indexes asynchronously).
# Target list comes from the same source of truth the build used.
VERSION="${{ steps.ext_version.outputs.next }}"
echo "Verifying Open VSX indexed v${VERSION}..."
for i in $(seq 1 18); do
FOUND=$(curl -s "https://open-vsx.org/api/Nskha/airtable-formula/${VERSION}" \
| node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
try{const p=JSON.parse(d);console.log(p.version||'not-found')}
catch{console.log('error')}
})")
if [[ "$FOUND" == "$VERSION" ]]; then
echo "✓ Open VSX confirmed: v${VERSION}"
break
fi
echo " not indexed yet (attempt $i/18), waiting 10s..."
sleep 10
TARGETS=$(node -e "import('./scripts/vsix-targets.mjs').then(m=>console.log(m.ALL_TARGETS.join(' ')))")

# ONE wall-clock budget shared by all targets, enforced whether or not
# anything has been confirmed yet. A per-target budget would multiply
# by the number of targets, so an index that never catches up (queued
# publish, namespace problem) would sleep ~24 minutes and emit a
# warning per target. This is a best-effort check that never fails the
# release, so it must not dominate the job's runtime.
VERIFY_BUDGET=180
VERIFY_START=$(date +%s)
UNVERIFIED=()

for TARGET in $TARGETS; do
echo "Verifying Open VSX indexed v${VERSION} (${TARGET})..."
FOUND=""
while true; do
FOUND=$(curl -s "https://open-vsx.org/api/Nskha/airtable-formula/${TARGET}/${VERSION}" \
| node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
try{const p=JSON.parse(d);console.log(p.version||'not-found')}
catch{console.log('error')}
})")
if [ "$FOUND" = "$VERSION" ]; then
echo "✓ Open VSX confirmed: v${VERSION} (${TARGET})"
break
fi
if [ $(( $(date +%s) - VERIFY_START )) -ge "$VERIFY_BUDGET" ]; then
UNVERIFIED+=("$TARGET")
break
fi
echo " not indexed yet, waiting 10s..."
sleep 10
done
done
if [[ "$FOUND" != "$VERSION" ]]; then
echo "::warning::Open VSX did not index v${VERSION} within 3 minutes — indexing may still be in progress (publish succeeded)"

ELAPSED=$(( $(date +%s) - VERIFY_START ))
if [ ${#UNVERIFIED[@]} -gt 0 ]; then
echo "::warning::Open VSX did not index v${VERSION} for ${UNVERIFIED[*]} within ${ELAPSED}s — indexing may still be in progress (publish succeeded)"
fi
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
Expand Down Expand Up @@ -358,8 +444,9 @@ jobs:
!inputs.dry_run &&
(inputs.target == 'extension' || inputs.target == 'both')
run: |
# Attach every platform build so users can sideload the right one.
gh release create "extension/v${{ steps.ext_version.outputs.next }}" \
"${{ steps.vsix.outputs.file }}" \
artifacts/*.vsix \
--title "Extension v${{ steps.ext_version.outputs.next }}" \
--generate-notes \
--target main
Expand Down Expand Up @@ -402,6 +489,13 @@ jobs:
fi
if [[ "${{ inputs.target }}" == "extension" || "${{ inputs.target }}" == "both" ]]; then
echo "- Extension: ${{ steps.ext_version.outputs.current }} → **${{ steps.ext_version.outputs.next }}**" >> $GITHUB_STEP_SUMMARY
if [[ -d artifacts ]]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo " Platform builds (no untargeted fallback is published):" >> $GITHUB_STEP_SUMMARY
for f in artifacts/*.vsix; do
echo " - \`$(basename "$f")\`" >> $GITHUB_STEP_SUMMARY
done
fi
fi
if [[ "${{ inputs.target }}" == "mcp-server" || "${{ inputs.target }}" == "both" ]]; then
echo "- MCP Server: ${{ steps.mcp_version.outputs.current }} → **${{ steps.mcp_version.outputs.next }}**" >> $GITHUB_STEP_SUMMARY
Expand Down
90 changes: 90 additions & 0 deletions .github/workflows/verify-binary-pins.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Verify native-binary pins

# Re-derive every platform package's `.node` digests from the registry and diff
# them against the committed pin file.
#
# ─────────────────────────────────────────────────────────────────────────────
# THIS JOB IS ADVISORY. IT IS NOT THE GATE. READ THIS BEFORE CHANGING EITHER.
#
# It is path-filtered, so on most pull requests it does not run at all — and a
# job that does not run reports as SKIPPED, which a merge queue or a required-
# check configuration can render as green. **A skipped run is not a
# verification.** Nothing about a green PR here proves the pins were checked.
#
# The authoritative check is the `Verify native-binary digest pins` step in
# .github/workflows/release.yml, which sits between `pnpm install
# --frozen-lockfile` and packaging. That one runs unconditionally on every
# extension release, and nothing publishes or tags if it fails. THAT is the
# safety boundary.
#
# Two consequences, both easy to get wrong later:
# - Do NOT promote this workflow to a required status check believing it
# gates merges. Path filtering means it will pass by absence.
# - Do NOT delete the release-workflow step believing this job covers it.
# It does not, and cannot.
#
# This was a deliberate trade. The alternatives — a companion always-succeeds
# job to make the skip explicit, or dropping the `paths` filter so it runs on
# every PR — each cost more (a permanently-green decoy check, or ~150 MB of
# tarball downloads on every unrelated PR) than they buy, precisely BECAUSE the
# release step is already the real gate. What this job buys is early warning for
# the contributor, in the PR that caused the problem.
# ─────────────────────────────────────────────────────────────────────────────
#
# WHY A SEPARATE, PATH-FILTERED WORKFLOW
#
# This is the only check in the repo that needs network access and pulls ~150 MB
# of tarballs, so it is deliberately kept out of `pnpm test` and out of ci.yml's
# per-OS matrix. It only has anything to say when the pins or the lockfile move,
# which is exactly what `paths` expresses — and GitHub applies `paths` at the
# trigger level, hence its own file rather than a job in ci.yml.
#
# WHY IT RUNS ON THE PR AS WELL AS AT RELEASE
#
# `pnpm-lock.yaml` changing is how an impit / @ngrok/ngrok bump arrives. With
# only the release-time check, a stale pin surfaces to whoever runs the release
# — long after the person who bumped the dependency has moved on. Here it
# surfaces to the author, in the PR that caused it, with the command to fix it.
# That is a convenience for the contributor, not a second safety boundary.
#
# WHAT IT UNIQUELY CATCHES
#
# A pin file that is internally consistent — correct version, correct integrity
# — but carries WRONG DIGESTS. The neighbouring failure modes are already
# covered offline and never reach here: a stale or missing pin fails closed in
# `binaryPin()` during vendoring, and a re-published tarball fails the lockfile
# SHA-512 on download. A poisoned pin file is the residual trust root, and only
# a fresh re-derivation from the registry can expose it.

on:
pull_request:
paths:
# Any of these can invalidate the pin file — two of them WITHOUT editing it:
- 'pnpm-lock.yaml' # an impit / @ngrok/ngrok bump
- 'scripts/native-binary-digests.json' # the pins themselves
- 'scripts/record-native-binary-digests.mjs' # HOW digests are produced
- 'scripts/vsix-targets.mjs' # WHICH packages are expected at all
- 'scripts/safe-symlinks.mjs' # the symlink-escape guard the check relies on
- 'scripts/vendor-platform-packages.mjs' # tarball fetch/verify/vendor logic the check imports
- 'package.json' # the `check:binary-digests` script definition itself

permissions:
contents: read

jobs:
verify-pins:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '22'

# No `pnpm install` on purpose: the checker uses only Node builtins, the
# repo's own scripts, `pnpm-lock.yaml` and `tar`. Skipping the install
# keeps this job to a fetch and a hash.
- name: Re-derive digests from the registry and diff against the pins
run: pnpm check:binary-digests
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@
**/PLAN.md
packages/mcp-server/.chrome-profile/
packages/mcp-server/dev-tools/
packages/extension/README.md
packages/extension/README.md
packages/webview/.ds-css/
packages/webview/.ds-src/
Loading
Loading