chore(spec): re-anchor the manifest, datasource, permission, dataset and webhook liveness ledgers to consuming symbols - #13038
Merged
Conversation
…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
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-trump
marked this pull request as ready for review
August 28, 2026 21:34
os-trump
enabled auto-merge
August 28, 2026 21:34
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>
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
mainafter #13035 landed, so every counter and gate reading below is measured on the real post-merge tree at heade904d280c.Census, from the gate's own counters
Base
93ea19bca(main, batch 1 included) → heade904d280c:The key-mention denominator moving by 34 is not incidental — see
webhook.jsonbelow.Per-entry table
⟳= citation was already wrong, repointed ·== citation accurate, grammar migration only ·+= citation was unparseable or path-only, now falsifiable for the first timemanifest.json— 34 citations, 22 rotted, 12 accurateidregistry.ts:3492—removeOverlayEntry's plain-key branchregistry.ts#installPackage·engine.ts#registerApp·package-routes.ts#publishRouteidengine.ts:4378— autonumber counter resyncnamespaceregistry.ts:3486— a loop body in the overlay removerregistry.ts#installPackage·#uninstallPackage·engine.ts#registerAppnamespaceengine.ts:4379— autonumber counter resyncdefaultDatasourceengine.ts:6337— secret-masking docblockengine.ts#resolveDatasourceBindingversionpackage-routes.ts:532— the #8016INTERNAL_ERRORdocblockpackage-routes.ts#publishRoute·service-package/index.ts#PackageServicePlugin·publish.ts#PluginPublishversionpublish.ts:91—const version = String(manifest.version …)versionindex.ts:394,401— comma-joined, never parsed as a citationtypestack.zod.ts:855·packages.ts:275#validateSingleApp·#handlePackagesRequestscopesettings-service.ts:800·package-writability.ts:79·studio.app.ts:72#registerManifest·#isWritablePackage·#STUDIO_APPnameengine.ts:4378— autonumber resyncengine.ts#registerApp·publish.ts#PluginPublishnamepublish.ts:92descriptionpublish.ts:121publish.ts#PluginPublish·validate.ts#Validatedescriptionvalidate.ts:354-355— a--jsonreachability commentpermissionssuggested-audience-bindings.ts:252#collectDeclaredSuggestionsobjectsengine.ts:4405-4420—createWithAutonumberResyncdocblockengine.ts#registerAppdatasourcesengine.ts:4391-4394— autonumber-collision docblockengine.ts#registerAppdependenciesprotocol.ts:4290-4300—evaluateRuntimeAuthoringGatedocblockprotocol.ts#resolveWritePackageScopecontributes.kindsengine.ts:4504-4518·registry.ts:3748(nowgetAllApps)engine.ts#registerApp→registry.ts#registerKinddataapp-plugin.ts:946-947— the job scheduling blockapp-plugin.ts#seedDatasetsnav.appengine.ts:4477-4480·registry.ts:3659-3667(uninstallPackage)#registerApp→#registerAppNavContribution·#applyNavContributionsnav.groupregistry.ts:3663—// Remove package record#registerAppNavContribution·#applyNavContributionsnav.priorityregistry.ts:3664—const collection = this.metadata.get('package')nav.itemsregistry.ts:3665—if (collection) {#getAppengine.objectstackprotocol-handshake.ts:87— the consumer's own signature line#resolveDeclaredRange·lint.ts#lintConfigengine.objectstacklint.ts:388engines.platformprotocol-handshake.ts:85— docblock ·lint.ts:387#resolveDeclaredRange·#lintConfigengines.protocolprotocol-handshake.ts:83— docblock ·lint.ts:386,396(comma-joined)#resolveDeclaredRange·#lintConfigpackagingbuild.ts:126+:159-161build.ts#PluginBuildTwo shapes worth naming.
protocol-handshake.ts:87had drifted ontoexport 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. Andcontributes.kindshas now had its line chased three times (:4499-4501at #10627, re-measured to:4504-4506for 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 accuratenamedatasource-connection-service.ts:674—onEnabledocblock#toSpec·#connectDeclareddriverdriver-factory.ts:345— a docblock*#resolveKind·#createDefaultDatasourceDriverFactoryconfigdriver-factory.ts:129— an error-message helper#toSpec·#buildSqlConnection·#buildMysqlConnection·#buildMemoryConfig·#buildMongoUrlpool.min/.maxdriver-factory.ts:191/192,:482/483,pool-support.ts:79#buildSqlPool·#createDefaultDatasourceDriverFactory·#POOL_UNSUPPORTED_DRIVER_IDSpool.idleTimeoutMillisdriver-factory.ts:193,pool-support.ts:79#buildSqlPool·#POOL_UNREAD_KEYS_BY_DRIVER·#POOL_UNSUPPORTED_DRIVER_IDSpool.connectionTimeoutMillisdriver-factory.ts:194,pool-support.ts:79ssl.enabled/.rejectUnauthorized/.ca/.cert/.keydriver-factory.ts:111/114/115/116/117— five consecutive lines of one docblock aboutOS_DATABASE_URL#resolveSslOption(+#mysqlSslOption)activedatasource-connection-service.ts:296— a bare}#connectDeclared·#assertDeclaredPoolsAreHonoured·admin-plugin#rehydratePoolsautoConnect:233— a comment about operators checking the database#isDatasourceAddressedschemaMode:679—const names = Array.from(this.states.entries())#toSpec·#isDatasourceAddressed·engine.ts#assertWriteAllowed·#registerDatasourceDefexternal.allowedSchemasexternal-datasource-service.ts:145— acol.primaryKeydocblock#listRemoteTablesexternal.allowWritesengine.ts:620— a bare docblock*engine.ts#assertWriteAllowed·#registerDatasourceDefexternal.validationexternal-validation-plugin.ts:153(aschemaMode?: stringfield) ·:231#resolveOnMismatch·#scheduleDriftChecksexternal.credentialsRef:457— a docblock about instance-field threading#attemptConnectexternal.queryTimeoutMsdatasource-admin-service.ts:220— a blank line#testConnectionorigindatasource-admin-plugin.ts:244—variant: 'secondary', a UI action#rehydratePools·#DatasourceAdminServicePluginEvery one of the 28 was wrong. The
sslblock 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.allowWritesdeserves its own line: the federation write gate is an enforcement point, and its only pointer had drifted ~4650 lines fromassertWriteAllowedin a 13,593-line file.permission.json— 25 citations, 20 rotted, 5 accurateobjects.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_KEYSfor transfer)objects.allowExportcurrent-user-endpoints.ts:493-502rest-server.ts#enforceExportPermission·security-plugin.ts#canExport·#annotateEffectiveApiOperationsobjects.viewAllRecordshasViewAllData, a symbol that no longer exists inpackages/**#PermissionEvaluator·#getEffectiveScope·#superuserBypassSetsobjects.modifyAllRecords/readScope#MODIFY_ALL_WRITE_KEYS·#getEffectiveScope·sharing-service.ts#buildReadFilterobjects.writeScopepermission-evaluator.ts:224— themodifyAllRecordsbypass, a different key's enforcement ·sharing-service.ts:230—noun: 'share'#getEffectiveScope·#buildWriteFilter·#matchesOwnerScopefields.readable/.editablepermission-evaluator.ts:301/:302— docblock lines#getFieldPermissions(one function, both keys)systemPermissions:267(insidegetEffectiveScope) ·sharing-rule-service.ts:136(private readonly inertRuleSeen) ·sharing-plugin.ts:993(an i18n docblock) ·current-user-endpoints.ts:897(the/me/appsheader comment)#getSystemPermissions·#assertCanManageRules·#buildSharingMiddleware·#registerCurrentUserEndpointstabPermissions:947:958:963:772:862in hono (all near-misses),permission-set-projection.ts:287(cachedSpecBodyKeys, ~30 off),assemble-execution-context.ts:328#registerCurrentUserEndpoints·#tabRank·#permissionSetBodyFromRow·#entryFields=×1rls.namerls-compiler.ts:203— a fail-closed comment#compileFilterrls.object/.operation/.positions:289/:304/:297#getApplicablePoliciesrls.using/.check/.enabled#compileFilter·#getApplicablePoliciessystemPermissionsis 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.:897is the instructive one — it landed on the handler's own header comment, five lines above the line reading1. 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 accurateEvery citation in this file landed on a docblock, a blank line, a brace, or another key's code. Selected:
name/labeldataset-compiler.ts:178();) /:179(})#compileDatasetobject:180—return m.aggregate as Metric['type'](a different key's read)#compileDatasetinclude:92— a blank line#compileDatasetfilterdataset-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·#compileDatasetdimensions.dateGranularity:146(/**) and the note'sdataset-executor.ts:272-287#compileDataset·#resolveDimensionGranularitymeasures.aggregate:60—export interface DerivedMeasureSpec {#aggregateToMetricTypemeasures.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/#evaluateDerivedMeasuresThree entries gained a missing half rather than just a repair:
filter,measures.filterandmeasures.derivedcited only the executor, which readscompiled.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 droppeddataset.filterfromcompileDatasetwould have left the old citation resolving happily against code that reads a name the author never writes.webhook.json— 0 parseable citations before this PRAll eleven
liveentries cited bare filenames with line numbers (bootstrap-declared-webhooks.ts:193,auto-enqueuer.ts:266,sys-webhook.object.ts:160).PATH_RErequires 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 ofobject.jsonin 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:
The check was right.
isActiveis remapped to the runtime columnactiveat 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 (akey-mention.baseline.jsonexemption) is outside this batch's declared file surface — so the dispatcher call site moved into the entry'snote, on theobject.tenancy.organizationFieldprecedent from batch 1: a consumer that never names the key belongs in prose, not inevidence.tool.json— deliberately unchangedEvery citation in
tool.jsonpoints intopackages/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 themservice-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 stampingverifiedAton 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_notesays the framework's own service-ai tree "is a stale build artifact with nosrc/" — 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:datasource.external.validation— the note said`checkOnBoot` gates the boot-time one. It does not.checkOnBoothas zero readers anywhere inpackages/**; its only non-test occurrences are the schema declaration, two alias spellings and one example app that authors it.ExternalValidationPlugin.starthookskernel:readyand callsrunValidationunconditionally, socheckOnBoot: falsestill runs the boot sweep — and under the defaultonMismatch: 'fail'that aborts boot. The container entry keepsliveon 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, socheckOnBoot: falsestill aborts boot under the defaultonMismatch: 'fail'#13037, not flipped here.permission.objects.viewAllRecords— its evidence named a readerhasViewAllData. That symbol no longer exists in the tree; the readers aresuperuserBypassSets, withhasSuperuserReadBypass/hasSuperuserWriteBypassbeside 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-bytesrun in addition because AGENTS.md mandates it for any edit.23 of 25 EXIT=0, plus
check:nul-bytesEXIT=0:check:livenessEXIT=0, printing its own verdict line: "everypath:NNNcitation names a line that file actually has, everypath#symbolanchor 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 afileswhitelist…"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-test2 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-scalepnpm 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:Ablation — the new anchors are genuinely checked
Gate-green alone would not prove it: an anchor token that stops matching
PATH_REdegrades 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 emptygit diff HEADplus a blob hash equal to the HEAD blob; absolute paths throughout, under anEXIT INT TERMtrap. Predicted direction before running: RED, naming the entry.No build leg is owed or claimed: the gate runs from source through
tsxand reads the JSON directly, so nodist/sits between the mutation and the reading.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:
patchon@objectstack/spec.livenessis named in that package'sfilesarray, so these ledgers ship inside the npm tarball — published data, andskip-changesetdoes not apply. Settled by measurement in batch 1 and carried forward here.Contract gate
None applies. The writing surface is
packages/spec/liveness/*.jsonplus one changeset — governance data, outside thepackages/spec/src/**path leg. No accept set moves, no schema key changes, no runtime behaviour changes. Clause-② is not reachable.Honest residual
isSymbolNamedonly 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.manifest.data→app-plugin.ts#seedDatasetsanchors a local const, because the legacy read is inline inAppPlugin.startwith no enclosing named helper (contrastcollectBundleActions, which batch 1 could anchor forobject.actions). Andpermission.tabPermissions/systemPermissionsboth anchorcurrent-user-endpoints.ts#registerCurrentUserEndpoints, because/auth/me/permissionsand/me/appsare anonymous route closures inside one exported registrar — the distinction the seven original lines carried now survives only in the entry's prose.#tabRankis 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.email_template.json23,api.json21,doc.json15,book.json13,query.json12,job.json11.tool.jsonis out of reach from this seat, not deferred by choice — see above. It needs a reader with the cloud repo.notefields were corrected only where the re-closure passed over them (thepoolblock note,datasource's_notereachability pointer,manifest.dependencies,contributes.kinds). Notes in entries this batch did not touch were left alone; a line inside anoteis strictly weaker than one inevidence, because no check readsnoteat all.Generated by Claude Code