Skip to content

chore(spec): re-anchor the manifest, datasource, permission, dataset and webhook liveness ledgers to consuming symbols - #13038

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-13003-anchor-adoption-b2
Aug 28, 2026
Merged

chore(spec): re-anchor the manifest, datasource, permission, dataset and webhook liveness ledgers to consuming symbols#13038
os-trump merged 2 commits into
mainfrom
claude/issue-13003-anchor-adoption-b2

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Part of #13003

Adoption batch 2 of the symbol-anchor citation grammar (#12516 / PR #13004), following batch 1 (#13035, action.json + object.json, merged). File-disjoint from batch 1 by construction: manifest.json (34 citations), datasource.json (28), permission.json (25), dataset.json (18), and the three undated files the batch-1 ACCEPT ruling sorts as OLDEST — dataset.json, tool.json, webhook.json.

Every citation below was re-closed by reading the code on the current tree. No line number was shifted by a diff; no existing citation was carried forward unread. 88 of the 105 parseable line citations were already pointing at the wrong place, every one of them IN RANGE and therefore invisible to the existence check, the line bound and the key-mention check alike.

Branch merged main after #13035 landed, so every counter and gate reading below is measured on the real post-merge tree at head e904d280c.

Census, from the gate's own counters

Base 93ea19bca (main, batch 1 included) → head e904d280c:

line citations: 280 → 175 (105 retired)
symbol anchors: 47 → 232 (185 added)
evidence paths: 410 → 444 repo-local, all resolving
key-mention: 410 → 444 pairs asked, 443 anchored, 1 exempt
verification clock: 427 entries dated (was 393); 277 undated (was 311)

The key-mention denominator moving by 34 is not incidental — see webhook.json below.

Per-entry table

= citation was already wrong, repointed · = = citation accurate, grammar migration only · + = citation was unparseable or path-only, now falsifiable for the first time

manifest.json — 34 citations, 22 rotted, 12 accurate

EntryFROM (and what that line is today)TO
idregistry.ts:3492removeOverlayEntry's plain-key branchregistry.ts#installPackage · engine.ts#registerApp · package-routes.ts#publishRoute
idengine.ts:4378 — autonumber counter resync
namespaceregistry.ts:3486 — a loop body in the overlay removerregistry.ts#installPackage · #uninstallPackage · engine.ts#registerApp
namespaceengine.ts:4379 — autonumber counter resync
defaultDatasourceengine.ts:6337 — secret-masking docblockengine.ts#resolveDatasourceBinding
versionpackage-routes.ts:532 — the #8016INTERNAL_ERROR docblockpackage-routes.ts#publishRoute · service-package/index.ts#PackageServicePlugin · publish.ts#PluginPublish
versionpublish.ts:91const version = String(manifest.version …)=
versionindex.ts:394,401comma-joined, never parsed as a citation+
typestack.zod.ts:855 · packages.ts:275#validateSingleApp · #handlePackagesRequest=
scopesettings-service.ts:800 · package-writability.ts:79 · studio.app.ts:72#registerManifest · #isWritablePackage · #STUDIO_APP=
nameengine.ts:4378 — autonumber resyncengine.ts#registerApp · publish.ts#PluginPublish
namepublish.ts:92=
descriptionpublish.ts:121publish.ts#PluginPublish · validate.ts#Validate=
descriptionvalidate.ts:354-355 — a --json reachability comment
permissionssuggested-audience-bindings.ts:252#collectDeclaredSuggestions=
objectsengine.ts:4405-4420createWithAutonumberResync docblockengine.ts#registerApp
datasourcesengine.ts:4391-4394 — autonumber-collision docblockengine.ts#registerApp
dependenciesprotocol.ts:4290-4300evaluateRuntimeAuthoringGate docblockprotocol.ts#resolveWritePackageScope
contributes.kindsengine.ts:4504-4518 · registry.ts:3748 (now getAllApps)engine.ts#registerAppregistry.ts#registerKind
dataapp-plugin.ts:946-947 — the job scheduling blockapp-plugin.ts#seedDatasets
nav.appengine.ts:4477-4480 · registry.ts:3659-3667 (uninstallPackage)#registerApp#registerAppNavContribution · #applyNavContributions
nav.groupregistry.ts:3663// Remove package record#registerAppNavContribution · #applyNavContributions
nav.priorityregistry.ts:3664const collection = this.metadata.get('package')
nav.itemsregistry.ts:3665if (collection) {↑ · #getApp
engine.objectstackprotocol-handshake.ts:87the consumer's own signature line#resolveDeclaredRange · lint.ts#lintConfig
engine.objectstacklint.ts:388=
engines.platformprotocol-handshake.ts:85 — docblock · lint.ts:387#resolveDeclaredRange · #lintConfig⟳ / =
engines.protocolprotocol-handshake.ts:83 — docblock · lint.ts:386,396 (comma-joined)#resolveDeclaredRange · #lintConfig⟳ / +
packagingbuild.ts:126 + :159-161build.ts#PluginBuild=

Two shapes worth naming. protocol-handshake.ts:87 had drifted onto export function resolveDeclaredRange( — its own consumer's signature: existence passes, the line bound passes, the key-mention check passes, and to a human opening the file it reads very nearly right. And contributes.kinds has now had its line chased three times (:4499-4501 at #10627, re-measured to :4504-4506 for the 2026-08-23 seeding, wrong again here) — chasing the line was the maintenance cost this migration removes.

datasource.json — 28 citations, 28 rotted, 0 accurate

EntryFROM (today)TO
namedatasource-connection-service.ts:674onEnable docblock#toSpec · #connectDeclared
driverdriver-factory.ts:345 — a docblock *#resolveKind · #createDefaultDatasourceDriverFactory
configdriver-factory.ts:129 — an error-message helper#toSpec · #buildSqlConnection · #buildMysqlConnection · #buildMemoryConfig · #buildMongoUrl
pool.min / .maxdriver-factory.ts:191/192, :482/483, pool-support.ts:79#buildSqlPool · #createDefaultDatasourceDriverFactory · #POOL_UNSUPPORTED_DRIVER_IDS
pool.idleTimeoutMillisdriver-factory.ts:193, pool-support.ts:79#buildSqlPool · #POOL_UNREAD_KEYS_BY_DRIVER · #POOL_UNSUPPORTED_DRIVER_IDS
pool.connectionTimeoutMillisdriver-factory.ts:194, pool-support.ts:79
ssl.enabled / .rejectUnauthorized / .ca / .cert / .keydriver-factory.ts:111 / 114 / 115 / 116 / 117five consecutive lines of one docblock about OS_DATABASE_URL#resolveSslOption (+ #mysqlSslOption)
activedatasource-connection-service.ts:296 — a bare }#connectDeclared · #assertDeclaredPoolsAreHonoured · admin-plugin#rehydratePools
autoConnect:233 — a comment about operators checking the database#isDatasourceAddressed
schemaMode:679const names = Array.from(this.states.entries())#toSpec · #isDatasourceAddressed · engine.ts#assertWriteAllowed · #registerDatasourceDef
external.allowedSchemasexternal-datasource-service.ts:145 — a col.primaryKey docblock#listRemoteTables
external.allowWritesengine.ts:620 — a bare docblock *engine.ts#assertWriteAllowed · #registerDatasourceDef
external.validationexternal-validation-plugin.ts:153 (a schemaMode?: string field) · :231#resolveOnMismatch · #scheduleDriftChecks
external.credentialsRef:457 — a docblock about instance-field threading#attemptConnect
external.queryTimeoutMsdatasource-admin-service.ts:220a blank line#testConnection
origindatasource-admin-plugin.ts:244variant: 'secondary', a UI action#rehydratePools · #DatasourceAdminServicePlugin

Every one of the 28 was wrong. The ssl block is the sharpest case: five consecutive pointers into a docblock about which host knob a connect-failure remedy should name, on a security-shaped block whose own recorded history is that it was once silently inert (#4465).

external.allowWrites deserves its own line: the federation write gate is an enforcement point, and its only pointer had drifted ~4650 lines from assertWriteAllowed in a 13,593-line file.

permission.json — 25 citations, 20 rotted, 5 accurate

EntryFROM (today)TO
objects.allowCreate/Read/Edit/Delete/Transferpermission-evaluator.ts:15 ×5 — a docblock about the 2026-08-26 ruling#OPERATION_TO_PERMISSION (+ #PermissionEvaluator, #crudBucketForOperation; #DESTRUCTIVE_OPERATIONS + #MODIFY_ALL_WRITE_KEYS for transfer)
objects.allowExportcurrent-user-endpoints.ts:493-502rest-server.ts#enforceExportPermission · security-plugin.ts#canExport · #annotateEffectiveApiOperations= / +
objects.viewAllRecords(path-only, no line) — and its prose named hasViewAllData, a symbol that no longer exists in packages/**#PermissionEvaluator · #getEffectiveScope · #superuserBypassSets+
objects.modifyAllRecords / readScope(path-only)#MODIFY_ALL_WRITE_KEYS · #getEffectiveScope · sharing-service.ts#buildReadFilter+
objects.writeScopepermission-evaluator.ts:224the modifyAllRecords bypass, a different key's enforcement · sharing-service.ts:230noun: 'share'#getEffectiveScope · #buildWriteFilter · #matchesOwnerScope
fields.readable / .editablepermission-evaluator.ts:301 / :302 — docblock lines#getFieldPermissions (one function, both keys)
systemPermissions:267 (inside getEffectiveScope) · sharing-rule-service.ts:136 (private readonly inertRuleSeen) · sharing-plugin.ts:993 (an i18n docblock) · current-user-endpoints.ts:897 (the /me/apps header comment)#getSystemPermissions · #assertCanManageRules · #buildSharingMiddleware · #registerCurrentUserEndpoints
tabPermissionsseven citations: :947:958:963:772:862 in hono (all near-misses), permission-set-projection.ts:287 (cachedSpecBodyKeys, ~30 off), assemble-execution-context.ts:328#registerCurrentUserEndpoints · #tabRank · #permissionSetBodyFromRow · #entryFields⟳ ×6, = ×1
rls.namerls-compiler.ts:203 — a fail-closed comment#compileFilter
rls.object / .operation / .positions:289 / :304 / :297#getApplicablePolicies=
rls.using / .check / .enabled(path-only)#compileFilter · #getApplicablePolicies+

systemPermissions is the measurement that most directly re-states the card: its four citations were set by a by-hand re-verification on 2026-08-23, and all four were dead five days later. :897 is the instructive one — it landed on the handler's own header comment, five lines above the line reading 1. AppSchema.requiredPermissions ⊆ ctx.systemPermissions. A near-miss onto prose that describes the read is the hardest form to catch, because anyone who opens the file to check finds the right words there.

dataset.json — 18 citations, 18 rotted, 0 accurate

Every citation in this file landed on a docblock, a blank line, a brace, or another key's code. Selected:

EntryFROM (today)TO
name / labeldataset-compiler.ts:178 ();) / :179 (})#compileDataset
object:180return m.aggregate as Metric['type'] (a different key's read)#compileDataset
include:92 — a blank line#compileDataset
filterdataset-executor.ts:205 — a docblock about blanks spreading#compileDataset (the authored key) · #DatasetExecutor (the compiled form)
dimensions.type:143 — a {@link compileDataset} docblock line#dimensionType · #compileDataset
dimensions.dateGranularity:146 (/**) and the note's dataset-executor.ts:272-287#compileDataset · #resolveDimensionGranularity
measures.aggregate:60export interface DerivedMeasureSpec {#aggregateToMetricType
measures.label / .format / .currencyanalytics-service.ts:479 / :480 / :531 — docblocks, up to ~830 lines off#compileDataset + #AnalyticsService (queryDataset)
measures.filter / .deriveddataset-executor.ts:225 / :247 (a blank line)#compileDataset (authored key) · #splitMeasuresByFilter / #evaluateDerivedMeasures

Three entries gained a missing half rather than just a repair: filter, measures.filter and measures.derived cited only the executor, which reads compiled.filter / compiled.measureFilters / compiled.derived — never the authored key. The read of the key itself is in the compiler, and was uncited. A change that dropped dataset.filter from compileDataset would have left the old citation resolving happily against code that reads a name the author never writes.

webhook.json — 0 parseable citations before this PR

All eleven live entries cited bare filenames with line numbers (bootstrap-declared-webhooks.ts:193, auto-enqueuer.ts:266, sys-webhook.object.ts:160). PATH_RE requires a repo-rooted token, so none of them was ever parsed as a citation: this ledger contributed zero local paths, zero line citations, zero key-mention pairs. The gate was not passing this file — it was never asked about it, and the ledger's own counters could not say so. Every entry was also undated.

All eleven are now repo-rooted anchors: #mapWebhookToRow, #bootstrapDeclaredWebhooks, #parseRow, #doRefresh, #attachSecret, #attachHeaders, #SysWebhook, #assertEnqueueDeliverable. The claims turned out to be true and the pointers merely unresolvable — the exact opposite of object.json in batch 1, where the pointers resolved and the claims were wrong. Both are the same defect at different ends.

And it paid inside this run. Making the citations askable made the key-mention check answer, and it went red immediately:

✗ 1 'live' citation(s) name a file that never names the property:
webhook/isActive → packages/plugins/plugin-webhooks/src/auto-enqueuer.ts

The check was right. isActive is remapped to the runtime column active at the materializer, and nothing downstream of that ever sees the authored spelling. Per the guidance's three options this is neither a repoint nor a re-classification, and option 3 (a key-mention.baseline.json exemption) is outside this batch's declared file surface — so the dispatcher call site moved into the entry's note, on the object.tenancy.organizationField precedent from batch 1: a consumer that never names the key belongs in prose, not in evidence.

tool.json — deliberately unchanged

Every citation in tool.json points into packages/services/service-ai/…, the closed cloud runtime. That directory does not exist in this checkout at all (verified: packages/services/ has 16 members, none of them service-ai), and the scanner classifies the prefix as foreign, so the file has zero local citations and zero anchorable pointers. There is no code here to re-close it against, and stamping verifiedAt on entries this seat cannot verify would be exactly the false-confidence the ledger exists to prevent. Left byte-for-byte untouched and reported on #13003. One incidental drift for whoever picks it up: the file's _note says the framework's own service-ai tree "is a stale build artifact with no src/" — it is now absent entirely.

Two prose claims withdrawn

Batch 1 found one evidence string falsified in prose as well as position (object.ownership). Batch 2 found two:

  1. datasource.external.validation — the note said `checkOnBoot` gates the boot-time one. It does not. checkOnBoot has zero readers anywhere in packages/**; its only non-test occurrences are the schema declaration, two alias spellings and one example app that authors it. ExternalValidationPlugin.start hooks kernel:ready and calls runValidation unconditionally, so checkOnBoot: false still runs the boot sweep — and under the default onMismatch: 'fail' that aborts boot. The container entry keeps live on its two real readers (onMismatch, checkIntervalMs); the inert member is below this ledger's one-level walk and is filed as datasource.external.validation.checkOnBoot is declared-but-unenforced — the boot sweep runs unconditionally, so checkOnBoot: false still aborts boot under the default onMismatch: 'fail' #13037, not flipped here.
  2. permission.objects.viewAllRecords — its evidence named a reader hasViewAllData. That symbol no longer exists in the tree; the readers are superuserBypassSets, with hasSuperuserReadBypass / hasSuperuserWriteBypass beside it. The citation was path-only, so nothing could ever have falsified it.

Evidence

All at head e904d280c (the merge commit that carries #13035). Exit codes captured before any pipe (redirect-then-read).

Gate family derived at that commit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, no paths passed — the script took its own change set from the merge base (6 path(s) vs merge base 93ea19bca, three-dot semantics). 25 families matched; check:nul-bytes run in addition because AGENTS.md mandates it for any edit.

23 of 25 EXIT=0, plus check:nul-bytes EXIT=0:

  • check:livenessEXIT=0, printing its own verdict line: "every path:NNN citation names a line that file actually has, every path#symbol anchor names a symbol its file contains, and every cited file names the property it is evidence for (or is a recorded exemption)"
  • check:published-filesEXIT=0"69 publishable package(s) of 78 workspace member(s) declare a files whitelist…"
  • also green: check:empty-state, check:strictness-ledger, check:variant-docs, check:changeset-gate-self-tests, check:objectui-changeset, check:merge-driver, check:pm-half-states, check:page-declaration-shape, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-keyed-text-bounds, check-comment-mask-adoption, check-plugin-teardown-shape, check-undeclared-dep-imports, check-affected-docs, check-drift-comment, release-rehearsal-clone --self-test

2 NOT MEASURED — explicit prerequisite exits, neither a red:

  • check-dev-prereqs.mjsEXIT=1"The workspace is not built — 1 unmet precondition, not a list of problems". Repo-scale pnpm build; this diff contains no TypeScript.
  • scripts/pm/check-half-states.mjsEXIT=3"PREREQUISITE NOT MET … Nothing was swept … It is not a clean board and it is not a dirty one — it is no reading at all."

Control-byte self-scan over every file in the diff (grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'): clean.

Vitest through the shared verify lock, at head e904d280c:

pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/liveness/
Test Files 10 passed (10)
Tests 254 passed (254)

Ablation — the new anchors are genuinely checked

Gate-green alone would not prove it: an anchor token that stops matching PATH_RE degrades to prose silently, taking the citation's existence check down with it. So one new anchor was mutated to a symbol its cited file does not contain. Committed first; mutation confirmed on disk by grep counts in both directions and by a blob-hash change, never by the editor's exit code; restore proven by an empty git diff HEAD plus a blob hash equal to the HEAD blob; absolute paths throughout, under an EXIT INT TERM trap. Predicted direction before running: RED, naming the entry.

No build leg is owed or claimed: the gate runs from source through tsx and reads the JSON directly, so no dist/ sits between the mutation and the reading.

HEAD blob = 19a2230bd29b4461cbb59c27042fb1fd987b3dd7 worktree = same (clean start)
BEFORE '#seedDatasets' = 1 ; '#collectBundleDatasets' = 0
AFTER '#seedDatasets' = 0 ; '#collectBundleDatasets' = 1
worktree blob = 32ee669f53d40419670823f9ffef5630b7e9aa06
MUTATION CONFIRMED ON DISK
MUTATED captured exit (before any pipe) = 1
symbol anchors: 232 pointer(s) written `path#symbol`, 231 naming a symbol the cited file contains, 1 UNRESOLVED.
✗ 1 anchored citation(s) name a symbol the cited file does not contain:
manifest/data → packages/runtime/src/app-plugin.ts#collectBundleDatasets
RESTORE '#seedDatasets' = 1 ; '#collectBundleDatasets' = 0 ; blob = 19a2230bd…
git diff HEAD empty · blob == HEAD blob → RESTORE CONFIRMED
RESTORED captured exit (before any pipe) = 0
symbol anchors: 232 pointer(s) written `path#symbol`, 232 naming a symbol the cited file contains.

The mutated run still counted 232 pointers, which independently proves the 185 new anchors parse as anchors rather than as prose.

Changeset

One is included: patch on @objectstack/spec. liveness is named in that package's files array, so these ledgers ship inside the npm tarball — published data, and skip-changeset does not apply. Settled by measurement in batch 1 and carried forward here.

Contract gate

None applies. The writing surface is packages/spec/liveness/*.json plus one changeset — governance data, outside the packages/spec/src/** path leg. No accept set moves, no schema key changes, no runtime behaviour changes. Clause-② is not reachable.

Honest residual

  • isSymbolNamed only checks that the cited file contains the identifier as a word. It cannot check that the symbol is the one enclosing the read, and it cannot check that the symbol's body still reads the key. Gate-green therefore does not prove these re-closures are correct — the hand reads do, and the per-entry table above is the record of them. The gate's own docblock states this residual; nothing here narrows it.
  • Two anchors in this batch are weaker than the rest, and are called out in their own notes rather than left to look uniform.manifest.dataapp-plugin.ts#seedDatasets anchors a local const, because the legacy read is inline in AppPlugin.start with no enclosing named helper (contrast collectBundleActions, which batch 1 could anchor for object.actions). And permission.tabPermissions / systemPermissions both anchor current-user-endpoints.ts#registerCurrentUserEndpoints, because /auth/me/permissions and /me/apps are anonymous route closures inside one exported registrar — the distinction the seven original lines carried now survives only in the entry's prose. #tabRank is cited beside the registrar for exactly that reason: it is specific to the tab machinery, so deleting the merge reds the anchor even though the registrar would survive.
  • The 105 → 0 conversion is per FILE, not per ledger. 175 line citations remain across the other ledger files; by count the next batches are email_template.json 23, api.json 21, doc.json 15, book.json 13, query.json 12, job.json 11.
  • tool.json is out of reach from this seat, not deferred by choice — see above. It needs a reader with the cloud repo.
  • Prose repairs are bounded by what was actually read. Line references inside note fields were corrected only where the re-closure passed over them (the pool block note, datasource's _note reachability pointer, manifest.dependencies, contributes.kinds). Notes in entries this batch did not touch were left alone; a line inside a note is strictly weaker than one in evidence, because no check reads note at all.

Generated by Claude Code

…3003 batch 2)
Adoption batch 2 of the #12516 symbol-anchor citation grammar: 105 `path:NNN`
citations across manifest / datasource / permission / dataset become
`path#symbol`, and webhook.json's eleven bare-filename citations become
repo-rooted anchors for the first time. Every citation re-closed by reading the
code on this tree; 88 of the 105 were already pointing at the wrong place, all
of them in range.
Part of #13003
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation tooling labels Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️5 changed file(s) yielded no anchor (packages/spec/liveness/dataset.json, packages/spec/liveness/datasource.json, packages/spec/liveness/manifest.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/spec/liveness/dataset.json, packages/spec/liveness/datasource.json, packages/spec/liveness/manifest.json, …) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 93ea19bcac33391439db0b7aa55c3333a9da0969packageMentionDocs.

@os-trump
os-trump marked this pull request as ready for review August 28, 2026 21:34
@os-trump
os-trump enabled auto-merge August 28, 2026 21:34
@os-trump
os-trump added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit 9ee2dcfAug 28, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13003-anchor-adoption-b2 branch August 28, 2026 21:58
os-trump pushed a commit that referenced this pull request Aug 28, 2026
File-disjoint merge: batch 2 touched manifest/datasource/permission/dataset/
webhook, batch 3 touches email_template/api/doc/book/query/job. Merged so every
counter, gate reading and the ablation transcript in this PR come from the same
tree the reviewer will see.
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/ltooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-trump@claude