…ral facts corrected
Flight 6 of the published-skills factual sweep. Every corrected claim was
settled against the implementing code (spec schemas, plugin-approvals,
service-automation, trigger-api / trigger-schedule, @objectstack/lint), never
against another document, with executed parse / CEL / schedule-normalization
probes where the claim is behaviour-bearing.
Net: -24 tokens, -4 lines across the package; the two ratcheted files that
moved stay under their existing ceilings (no ceiling touched).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
Fixes #13793
Part of #13658 — flight 6 of the published-skills factual sweep (
skills/objectstack-automation/**, 4 files, 1,163 lines).Governed surface: this PR stays DRAFT for human merge, and
needs:contract-reviewis attached to the PR and to card #13793 in the same stroke (clause 2 CONTENT limb, 批 #12). No self-clear.Method
Every claim below was settled against the implementing code —
packages/specautomation/data schemas,plugin-approvals,service-automation,trigger-api/trigger-schedule,@objectstack/lint— never against another document. Behaviour-bearing claims were settled by an executed probe (schema parse, CEL evaluation, schedule normalization) run against the freshly built@objectstack/specand@objectstack/formuladists. Probe files lived in a scratch dir inside the worktree and were deleted before the gate derivation; nothing probe-related is in this diff.Order followed the calibration comments: cross-file contradiction scan first, then parse-the-surface probes in both directions, then batched executable-oracle rows, with
evals/**at full weight.Corrections — landing site, before, after
11 distinct false facts across 12 landing sites.
SKILL.mdL134next:on a node "is ignored"FlowNodeSchemaisstrictObject— executed parse returnsUnrecognized key(s) on this flow node: nextSKILL.mdL189-191schedulekey "is silently stripped and the flow never binds"FlowSchema.parsereturns the named error plus theguidance.scheduleprescription (protocol-17 strict-unknown-keys, ADR-0078)SKILL.mdL479org_membership_level= "the better-auth org-membership tier — value is one ofowner/admin/member, and nothing else"owner/admin/delegated_admin/member"ORG_MEMBERSHIP_LEVELSderives fromBUILTIN_MEMBERSHIP_ROLES= 4 tiers (ADR-0108 / ADR-0105 D8);expandMembershipTierUsersfilterssys_memberon the tier verbatim, sodelegated_adminroutes. The spec source explicitly retires the "better-auth's closed set" attribution and the stale three-value copySKILL.mdL484queuelisted as a working approver type ("A data-ownership queue")resolveApproverSpechas noqueuebranch — the slot warns and routes to nobody (#3508); the type is inNON_AUTHORABLE_APPROVER_TYPESand published asxEnumDeprecatedso designers stop offering itSKILL.mdL604minApprovals"Default1"quorum,1per groupisApprovalSatisfied:quorumcomputesMath.min(Math.max(1, config.minApprovals ?? n), n)— the omitted default isn, not 1; onlyper_groupis?? 1. Its own doc comment reads "quorum — default = all"SKILL.mdL608decisionOutputsdeclaration enumerated as{ key, label?, type, multiple? }required?added to the enumerationDecisionOutputDefSchemadeclaresrequired, and it is the one key of the four the runtime enforces (an approve carrying no value for a required key is rejected). The same file already teachesrequired: truein prose and in theos:checkexample, so the enumeration contradicted its own fileSKILL.mdL660queuecapability every inbound POST "returns 503queue_unavailable"SERVICE_UNAVAILABLE"queue_unavailablereturns exactly one hit — this skill line itself.api-trigger.tsreturnsSERVICE_UNAVAILABLE(no queue registered) orENQUEUE_FAILEDSKILL.mdL692-698record-before-write/record-after-writerow addedtriggerTypeToHookEventsacceptsrecord-(before|after)-(create|insert|update|delete|write);writeis the create-OR-update union (#3427) that binds BOTH hooks.@objectstack/lintprescribes it by name: "For 'created or updated' use record-after-write (one flow, both events)". The table hid a shipped capabilitySKILL.mdL721OLD/NEW"were removed in M9.5 and now evaluate tonull"OLD.status == null,OLD == null,NEW == nulleach abort withUnknown variable: OLD/: NEW. Strict CEL faults on an unbound root; it never yieldsnull, so the old text taught a silent-false where the platform aborts loudlySKILL.mdL846SKILL.mdL964packages/lint/src/validate-flow-template-paths.tsimplements three build rules over that dialect (flow-template-unknown-field— an error inside a CRUDfilter— plus-lookup-traversaland-field-unprovisioned), andflow-double-brace-interpolation/flow-bare-dollar-referencecover its other two misuses. What is genuinely unchecked is only the call-position name, which is exactly what the sentence needed to sayevals/README.mdL11waitnode"approval_revisewindow"waitprescription was reversed by its 2026-07-28 amendment (#3823).APPROVAL_REVISE_NODE_TYPEis registered byplugin-approvalswithresumeAuthority: 'service', areviseedge into anything else is an authoring-time error, andsendBackrefuses the metadata.SKILL.mdand the eval file itself both already saidapproval_revise— only the index row still carried the retired prescriptionBudget offset (in-list, not a correction)
The token ratchet is shrink-only and its own message requires new text to be paid for by genuine deletion in the same file. One deletion pays for the additions above:
SKILL.mdL619-625, "Branching, side-effects & rejection"reviselabel,approval_revisewindow, "not a plainwait", back-edge, DAG-cycle exclusion,maxRevisionsdefault)No fact is lost at package level: every deleted sentence is stated in that section and in the
maxRevisionsnode-config row. It also removes the second copy of precisely the fact whose third copy — the eval index row — is item 12 above.Inventory and density
Claim carriers, counted mechanically over the four files: 78 table/enumeration rows · 42 bullets · 49 numbered rules · 14 code fences, plus roughly 22 callout sentences. Excluding non-behavioural scaffolding (eval format list, contributing notes, planned-structure tree) the behavioural inventory is about 205 claims.
triggers-capability gating of trigger arming, the run-summaryactedmetric); CLI end-to-end verdicts (about 9 — whatos validate/os build/os migrate meta --from 16actually print on a scaffolded project); Studio/designer rendering (about 6 — which picker eachxRefkind renders); cross-repo showcase-app pointers (about 3). Recorded, never silently skipped.Non-vacuity control (claims proven true by execution)
normalizeSchedule('0 9 * * *')returns{ type: 'cron', expression: '0 9 * * *' }, while the same call on the cron tagged template's envelope —{ dialect: 'cron', source: '0 9 * * *' }— returnsnull, i.e. "no recognizable schedule descriptor, not bound". Both halves of L191-193 ("a bare cron string also works"; "do NOT use the cron tagged template, its envelope is not a recognized shape") confirmed in one run, in both directions.FlowNodeAction.options.length === 20, matching L64 and the four node tables exactly.TimeRelativeTriggerSchemarefuses bothoffsetDays+withinDaystogether and neither of them, with the message "Provide exactly one of ..." — L771 confirmed in both error directions.previous.status != 'escalated' && record.status == 'escalated'evaluatestrueagainst the trigger scope — L717 / L725-727.os:check-markeddefineFlowexample (L534-579) parses clean underFlowSchema, decision outputs and all.behavior: 'first_response',lockRecord: true,onEmptyApprovers: 'admin_rescue',maxRevisions: 3— L604-611 confirmed row by row.Also confirmed VERIFIED and left untouched, since each was a live falsehood candidate: the readonly-strip block (L166-176, matched against
validate-readonly-flow-writes.tserror/warning split), the FSM introspection endpoint's overloadednext: null(L280-283, matched against the REST handler), the resume 403 for a service-owned pause (L455-459,PERMISSION_DENIEDto 403 in the dispatcher's own mapping), all six named lint rule ids,maxRecordsdefault 1000 and the daily 08:00 UTC sweep cadence, all fourrequires:capability tokens, and thenode_modules/@objectstack/spec/src/**pointers (the package really does publishsrc/**/*.zod.ts, so these are not the dist-only phantom paths flight 1 found elsewhere).references/_index.mdis generator-owned and was not hand-edited; its sync is asserted by its owning gate below, not by inspection.Verification
Gate families derived from the REAL diff in this fresh worktree, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— 14 families, no STALE warning, and the run confirms "no path-derived mandate" for this surface.Union re-run at final head e197523 (byte-identical tree to the earlier 1382e479 run):
check-test-completenessexit 3 is that gate's own PREREQUISITE-NOT-MET code — it grades a savedturbo run testlog, this family names it with no argument, and it exits before parsing a single line. Recorded as NOT MEASURED, per the script's own instruction; it is not a red.Two gates were red on their first run for a build prerequisite, not a finding, and both went green once the prerequisite was met:
check:doc-formula-expressionsneeded@objectstack/lintbuilt, andcheck:skill-examplesneeded@objectstack/client-reactbuilt. Both builds ran through the shared verify lock.Gate readings quoted above are each gate's own verdict line with the exit code captured before any pipe.
Sync and population gates
check:skill-refsgreen: "9 generated files in sync with packages/spec" —references/_index.mdneeds no regeneration and was never hand-edited.check:skill-examplesgreen: "260 prose examples type-check across 3 surface(s)". Population for this package: exactly 1os:check-marked fence (thedefineFlowdynamic-approval example inSKILL.md); the other three files carry zero markers. This diff touches no marked fence — verified withgit diff -U0 | grep os:check, which returns nothing — so the package's contribution to that population is unchanged and green.Budget
SKILL.mdtokensevals/README.mdtokensNet line budget across the package: -4, inside the "at most 0" constraint. No ratchet ceiling was raised or touched — the additions are paid for by the deletion listed above.
Changeset
None, and
skip-changesetis applied at creation. Verified rather than assumed: the diff is pureskills/**and publishes nothing from any package,skip-changesetis a real mechanism in this repo (thechangeset-checkjob in.github/workflows/pr-automation.ymlexempts on it), and the three merged predecessor sweeps carry no changeset — for example 597020a (#13777) shipped one file,skills/objectstack-ui/SKILL.md, alone.Deviations and notes
session_01EnE7G31tqbxN1rqpQmzurTand this branch, so a second comment would add no anti-duplication signal. Recorded here and in the report, as flight 5 did.issue_read get_comments(the payload channel serves only the first 15 timeline items and Published-skills factual sweep: verify every behavioral claim in skills/** against the implementation — program anchor #13658 has 33). Label writes therefore go through MCP with a read-back rather than the additive REST endpoint.skills/objectstack-automation/**.http_request"survives only as a deprecation-window alias" (L96). The alias itself was removed —AutomationEngineno longer registers those executors — but the ADR-0087 conversionflow-node-http-callout-rename(toMajor 11) still rewrites the token tohttpinsidecanonicalizeStoredFlow, whichregisterFlowcalls, so an authoredhttp_requestdoes still reach thehttpnode. "Alias" versus "conversion" is a mechanism nuance rather than a behavioural falsehood, and confirming which one an author meets end to end needs the boot harness this flight does not have. Recorded as NOT MEASURABLE rather than rewritten on a guess.Generated by Claude Code
Generated by Claude Code