docs(skills): objectstack-data factual sweep (3/3) — hooks reference, datasources, and the remaining rule files - #13698
Merged
Conversation
… datasources, and the remaining rule files
Four measured-false behavioral claims corrected against the implementation:
- data-hooks.md: ctx.log is documented as { info, warn, error } in two places.
It has FOUR levels — quickjs-runner.ts wires ['debug','info','warn','error']
and its own comment reads '[#7661] FOUR levels, not three'.
- data-hooks.md: the sandbox ctx.api.object(n) method table omits three real
methods — aggregate (api.read), updateMany and deleteMany (api.write).
- datasources.md: 'field.columnName on managed objects is unaffected' — the key
was removed in the 16.x line and is a parse error on ANY object.
- datasources.md: 'external settings are ... forbidden otherwise' — a managed
datasource carrying an external block parses fine. The required half holds.
The other six files in this PR's scope (validation.md, hooks.md, lifecycle.md,
naming.md, evals/README.md, references/_index.md) were inventoried and verified
with no falsehood found, so they carry no edit.
Token ratchet: both edited files shrink (-27 / -8).
Fixes #13675
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
This was referenced Aug 31, 2026
os-zhuang
approved these changes
Aug 31, 2026
zhuangjianguo
marked this pull request as ready for review
August 31, 2026 07:17
zhuangjianguo
enabled auto-merge
August 31, 2026 07:17
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.
Fixes #13675
Program anchor: #13658 — flight ② of the published-skills factual sweep. This is the final PR of three at whole-file boundaries, so it is the one carrying the closing keyword; the anchor #13658 stays open and keeps the roster. The earlier two are #13687 (
rules/field-types.md,rules/relationships.md,rules/indexing.md) and #13694 (SKILL.md). All three are based onorigin/mainand touch disjoint files — no stacking.With this PR the package is fully swept: 12 of 12 files, 4,935 lines.
Session, for durable attribution:
https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63deGoverned surface — human merge, deliberately parked
skills/**is the published, customer-facing surface. This PR is a draft on purpose: auto-merge is not armed, it is not marked ready, no review is requested, and this seat will not arm it. A draft awaiting the maintainer's own merge is the correct terminal state here, not a stuck PR.Scope: eight files, two of them edited
This PR's scope is everything the first two did not take:
references/data-hooks.md(1,446 lines),rules/validation.md(432),rules/hooks.md(209),rules/lifecycle.md(156),rules/naming.md(107),rules/datasources.md(80),evals/README.md(11) andreferences/_index.md(65, generator-owned — measured, never hand-edited). All eight were inventoried and verified; four falsehoods were found, all of them in two files. The other six carry no edit because nothing in them measured false — which is a result, not a skip, and the evidence is below.Non-vacuity control — claims proven TRUE by execution
1. The hook capability vocabulary is exactly five tokens, and the sixth is gone by name.
Both
rules/hooks.md("the five legal tokens … There is no hashing capability:crypto.hashwas removed in spec 17 because the sandbox never implemented it") anddata-hooks.md("There is nohttp.fetchcapability by design") are exactly right, down to the reason.2. Exactly eight lifecycle events, and the four the docs say do not exist really do not.
3. The
onError: 'abort'default really is unconditional — the reference's most emphasized claim. It states the default is'abort'"unconditionally … forafter*hooks too". Parsed both:Same run confirms
prioritydefault100andasyncdefaultfalse, both as documented, andonError: 'ignore'is refused ("expected one of abort|log").4. The
bodyenvelope's numeric bounds, exactly as written.capabilitiesreally does default to[],languagereally is exactly the two documented dialects, and the documented ceilings are the real ones. The hook/action timeout defaults the doc quotes are also real:quickjs-runner.ts:51-52declaresDEFAULT_HOOK_TIMEOUT_MS = 250andDEFAULT_ACTION_TIMEOUT_MS = 5000.5.
rules/validation.mdis exactly right about the rule vocabulary — all six discriminators and all three refusals.The
priority: 100default, theevents: ['insert','update']default, the "nodeleteevent", the "the key isregex, notpattern", the four built-in formats, and the conditional's single-nested-rule shape are all measured true.6.
rules/lifecycle.mdis right about all five classes AND all four of its "rejected at parse time" examples — each with a passing control.Every one of that file's parse-time refusals fires for the stated reason, and every control passes.
lifecycle.mdneeded no correction at all.7.
ctx.title'snullcontract, which looked wrong and is not. The table types itPromise<string | null>and says "nullwhen there is no title — it never falls back to the id", while the host seam resolvesundefined. The implementation settles it in favour of the doc (script-runner.ts):The skill documents the VM-facing contract, which is the one a body author sees. VERIFIED, not corrected.
The four FALSE claims, and what measured them
data-hooks.mdL372 (sandboxctxtable)ctx.log…{ info, warn, error }"ctx.log…{ debug, info, warn, error }"data-hooks.mdL450 (capabilities table)log…ctx.log.info/warn/error(msg, data?)"log…ctx.log.debug/info/warn/error(msg, data?)"data-hooks.mdL391-397 (ctx.api.object(n)method table)findfindOnecountinsertupdateupsertdeleteaggregate·updateMany·deleteMany— "also installed; samewhereshape"datasources.mdL37field.columnNameon managed objects is unaffected"field.columnNamedoes not exist — on ANY object … authoring it is a parse error everywhere, not only on a federated object"datasources.mdL21externalsettings are required iffschemaMode !== 'managed'(and forbidden otherwise)"externalsettings are required whenschemaMode !== 'managed'."1.
ctx.loghas four levels, not threeDocumented as
{ info, warn, error }in the sandboxctxtable and again asctx.log.info / warn / errorin the capabilities table. The runtime wires four, and its own comment says so in as many words (packages/runtime/src/sandbox/quickjs-runner.ts:639):The host seam agrees —
script-runner.tstypeslog?: { debug, info, warn, error }. The history here is the sharpest part: the same docblock records thatdebug"was thecrypto.hashshape one member over — inferred by the extractor and taught by the docs table with nothing installed behind it, so the one call it typed threw inside the VM. Unlike hashing it was ENFORCED rather than removed (ADR-0049)". So the docs once over-claimeddebug, the platform closed the gap by implementing it, and the docs then over-corrected past the new truth. A body callingctx.log.debug(...)works today and has its capability inferred; the table said it did not exist.2. The sandbox repo table omits three installed methods
###ctx.api.object(name)— the cross-object repointroduces its table with "Methods:", which reads as the surface. Measured against whatinstallCtxactually installs (quickjs-runner.ts:527-530):Ten methods are installed; the table listed seven.
aggregateis gated byapi.read,updateManyanddeleteManybyapi.write— so the two capability rows were incomplete in the same way and were corrected too. The added row is deliberately one line rather than three, because the file is at its ratchet ceiling.Deliberately NOT changed, and recorded instead: pattern 8 carries the comment "There is NO
updateMany— bulk updates useupdate(data, { where, multi: true })", inside ahandler:(in-process) example. The measurement above is of the sandbox wrap; this flight did not measure the in-processctx.apirepo's own surface, and the file elsewhere claims the two forms are "the same repository". Correcting that comment on inference would be exactly the document-vs-document reasoning the sweep forbids, so it is left alone and reported as NOT MEASURABLE with the tension named.3.
field.columnNameis gone everywhere, not just on external objectsThe Column-mapping section framed
columnNameas a federated-object mistake and then reassured the reader that "(field.columnNameon managed objects is unaffected.)". Measured on a plain managed object — nodatasource, noexternal— with a control:The reassurance was the whole problem: it told an author that a key which is a hard parse error is fine in the common case. The corrected bullet keeps the
external.columnMapprescription (which is right) and states the real scope.4. The "forbidden otherwise" half of the
externalrule does not holdexternalsettings arerequired iff schemaMode !== 'managed'(and forbidden otherwise). The required half is real and the refusal is exact; the forbidden half is not:A managed datasource carrying an
externalblock parses cleanly. Corrected to state only the half that holds — a smaller claim, and the one the platform actually enforces.The six files with no edit, and why that is a result
rules/validation.mdregex-not-patternkey, the four built-in formats, the conditional shape. 0 false.rules/lifecycle.mdrules/hooks.mdcrypto.hashremoval and its reason, the eight events, thebodyshape. Its abbreviatedctx.apilist is marked in-file as "Sandbox essentials (full contract inreferences/data-hooks.md)", so it reads as a pointer rather than an enumeration; the authoritative table is the one corrected above. 0 false.rules/naming.mdsnake_caseidentifier regexes and the option-value rule. 0 false.evals/README.mdreferences/_index.mdpnpm --filter @objectstack/spec gen:skill-refs) — measured, never hand-edited. Itsnode_modules/@objectstack/spec/src/**pointers all resolve: a realnpm pack --dry-run --jsonreports 209src/entries in the tarball and every.zod.tspath this package names is present.check:skill-refsis green on this diff.Written to the token ratchet, not around it
Both edited files sat at zero headroom and both shrink. The
data-hooks.mdpayment is a genuine deletion: actx.logwarning duplicated verbatim ~180 lines apart (the sandboxctxtable already says "ctx.logis an object, not callable asctx.log(msg)", and the Troubleshooting section repeated it in a parenthetical), plus asession.rolesrationale stated three times in one file, trimmed to once-in-full plus two short references. No ceiling was raised.references/data-hooks.mdlinesrules/datasources.mdlinesobjectstack-data/**package lines (all.md)references/data-hooks.mdtokens (ceiling 12611)rules/datasources.mdtokens (ceiling 911)The ratchet's own verdict lines at head
d66ac1010:Gates — derived, not recalled, run locally at head
d66ac1010Family derived from the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, harvested with--commands. 14 families this time, not 13 — touchingreferences/**pulls incheck:skill-refs, which the earlier two PRs' paths did not. 13 green, 1 NOT MEASURED; exit codes captured before any pipe:check:skill-refsquotes itself:✅ 9 generated files in sync with packages/spec— the reading that matters most here, becausereferences/_index.mdis generator-owned and this PR edits its sibling.Two more beyond the derived family, because they read this corpus:
check:skill-examplesis load-bearing here:data-hooks.mdcarries anos:checkblock (the copy-pasteafterUpdatehook), and this PR edits that file.check-test-completeness.mjsexits 3 with its own text "PREREQUISITE NOT MET — this gate grades a savedturbo run testlog, and no log was named". Recorded as NOT MEASURED, not as a red.check-nul-bytesclean:OK (scanned 7561 text file(s) … no raw ASCII control bytes), plus a direct control-byte scan over both changed files with no hits.ESLint was not run repo-wide, and the narrowing is measured. Same three readings: (1) no
files:block ineslint.config.mjsnames.md, and the strings.md/markdownappear nowhere in it; (2)--format jsonover both changed files returns 2 results, 0 total errors, each with oneruleId: nullmessage reading "File ignored because no matching configuration was supplied."; (3) type-aware linting is not enabled anywhere (noparserOptions.project), so this diff cannot move a verdict on an untouched file.No changeset
Pure
skills/**documentation correction, releasing nothing from any package.skip-changesetis applied.Calibration — the flight's closing numbers
Same counting rule throughout: one claim per assertable proposition about platform behaviour; front-matter, navigation, routing tables and pure style prescriptions excluded.
~92 NOT MEASURABLE across the package, recorded rather than skipped; the rest VERIFIED, with executed probes wherever the claim is behaviour-bearing.
Against flight ①'s 3.0% and its ~8x table concentration, the two findings that should re-size flights ③–⑫:
maskingRule"pruned" but live,titleFormat"retired" but deprecated,ctx.logthree levels of four, the repo table seven methods of ten,columnName"unaffected on managed" but universally removed, roll-ups "not available" onlookup,external"forbidden otherwise" when it is not). That direction hides shipped capability from an AI author, and no "grep for retired things" pass finds it. Both directions collapse to one cheap probe: for every surface the document names, parse it and read what the schema says back.indexing.mdvsrelationships.mdon FK indexing;relationships.mdvsSKILL.mdon master_detail requiredness;SKILL.md's tenancy example vs its own RLS sections onorganization_id;data-hooks.md's capability rows vs its own method table). None was decided document-vs-document — every one was then settled against the implementation — but the disagreement costs nothing to spot and pays for the probe.@objectstack/specreally does publishsrc/**/*.zod.ts(209src/entries measured in a realnpm pack --dry-run --json), unlike@objectstack/formula.What is not here, on purpose
relationship/master-detail-requiredis a lint warning whileobject.zod.tsargues the unguarded shape "arms the worst measured failure shape", with hard enforcement only undersharingModel: 'controlled_by_parent'; (b)SEARCHABLE_ENUM_TYPEScontains'status', which is not a member of the 49-valueFieldTypeenum — a dead vocabulary entry of the ADR-0049 shape. The skill reproduces that list faithfully, so its sentence is VERIFIED; the dead entry belongs to the implementation.data-hooks.md's hook-overhead table (0 hooks ~1ms, 5 ~5ms, 20 ~20ms) andindexing.md's "Max Indexes by table size" are recorded NOT MEASURABLE and left alone: they read as measurements and no repo artifact settles them, but rewriting unverifiable advice is not a factual correction and would spend ratchet budget the corrections need.Generated by Claude Code