Skip to content

docs(skills): objectstack-platform states load-time refusal, not silent stripping - #10038

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-9907-definestack-throws
Aug 20, 2026
Merged

docs(skills): objectstack-platform states load-time refusal, not silent stripping#10038
os-warren merged 3 commits into
mainfrom
claude/issue-9907-definestack-throws

Conversation

@os-warren

@os-warrenos-warren commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes#9907

The published objectstack-platform skill taught that an undeclared top-level
defineStack key is silently stripped and therefore a harmless no-op. That has
not been true since the outermost strict door closed: the parse refuses the key
and the stack does not load. The direction of the error is the harmful one — the
doc understates the failure, so an agent reading it ranks "the stack does not
load" as cosmetic, or spends a debugging pass hunting a runtime symptom that a
load-time refusal already produced loudly.

The prescription at every site was already correct and is unchanged (drivers are
plugins, not a driver: key). Only the stated mechanism moves.

Re-measured, not recalled

Both legs were measured in a worktree against a freshly built
@objectstack/spec, built under the shared verify lock
(VERDICT command-exit 0).

Runtime legdefineStack({ manifest, driver: {…} }):

defineStack validation failed (1 issue):
✗ (root): Unrecognized key(s) on this stack definition: `driver`. Until #8687
closed this surface (the outermost #4001 door), an unknown top-level stack
key parsed green and its value was silently dropped — a one-character typo
could ship an artifact missing a whole metadata family while `os validate`
exited 0. The declared keys are enumerated by `ObjectStackDefinitionSchema`
(@objectstack/spec, stack.zod.ts) and in the stack-definition reference docs.

Compile leg — the same literal through tsc --noEmit against the built
dist/index.d.mts, exit 2:

probe.ts(5,3): error TS2353: Object literal may only specify known properties,
and 'driver' does not exist in type 'ObjectStackDefinitionInput'.

Negative control for the compile leg: the identical probe with driver: removed
compiles clean, exit 0 — so the probe measures the key, not a broken setup.

The phantom-key example quoted in SKILL.md was re-measured separately, against
a rebuilt spec, with policies as the offending key (see "Second commit" below);
its control — the same stack with the phantom key deleted — loads normally.

The card's secondary premise did not survive re-measurement

The card asked for the general sentence to be split as "top level refuses;
ObjectSchema / FieldSchema still warn". Measured, they do not warn — they
refuse too
, on the same parse path:

✗ objects.0: Unrecognized key(s) on this object: `bogusObjectKey`. Until #4001
closed this shape these were dropped silently on the PARSE path …
✗ objects.0.fields.title: Unrecognized key(s) on this field: `bogusFieldKey`.
Until #4001 closed this shape these were dropped silently …

warnUnknownAuthoringKeys is still live, but its population has shrunk to the
shapes that are still strip-mode. Its top-level half is now deliberately silent
by construction — lintUnknownStackKeys returns [] unless the stack schema's
posture is strip, measured [] here, so the strict root reports once rather
than twice. listLintableAuthoringCollections() reports exactly two collections
left: connectors and views.

A live warn case on connectors — warns, does not throw, value gone after the
parse:

WARN: defineStack: connectors.stripe.bogusConnectorKey: 'bogusConnectorKey' is
not a declared connector key, so its value is dropped at load.
NO THROW.
bogusConnectorKey survived the parse? false

So the page states the split as measured — refused vs warned-then-dropped — and
is not flipped wholesale in either direction.

Sites changed

Four, all the same defect class. Three are the card's; the fourth came from the
prescribed sweep of the rest of the skill directory and is named here rather than
landed quietly.

SiteWasNow
rules/bootstrap-patterns.md (opening)"unknown keys are silently stripped by strict parsing, so a driver: entry is a no-op"prescription kept; mechanism restated as load-time refusal, quoting the measured error and the TS2353 line
rules/bootstrap-patterns.md (heading)"❌ Incorrect — driver: Key (Silently Stripped)""❌ Incorrect — driver: Key (Rejected at Load)"
rules/bootstrap-patterns.md (inline comment)"// ❌ Not a defineStack key — no-op""// ❌ Not a defineStack key — defineStack throws"
SKILL.md Full Configuration Reference"Unknown keys are silently stripped by strict parsing — a phantom key … is a no-op, not an error."the measured per-surface split, with the refusal output and the live connector warning
SKILL.md Feature Flags"(strict parsing silently strips unknown keys)""(writing one is refused at load, not stripped)"

The fourth site qualifies under the bounded in-place exemption: same defect class
as the card, mechanical with the correct form already pinned by the measurement
above, no other claim on the file, same gate family, no new verification surface.
A closing rescan of skills/objectstack-platform/ for silently strip|stripped by strict|no-op|Silently Stripped leaves two hits, both the negations this PR wrote.

Second commit — ADR-0090 D3 ratchet

CI caught a real defect in the first commit, not a flake. The quoted refusal used
the D3 reserved word as its example key, taking SKILL.md from its baselined 2
occurrences to 3:

check-role-word: 1 problem(s)
• skills/objectstack-platform/SKILL.md: role-word count grew 2 → 3.
New occurrences are banned (ADR-0090 D3).

The example key in the quote is now policies, which the sentence directly above
it already uses as its sibling example. This is not an edited quote: the
refusal was re-measured live against a rebuilt @objectstack/spec with
policies as the offending key, and the output is reproduced as measured. The
two pre-existing occurrences are untouched and scripts/role-word-baseline.json
is not modified — the ratchet returns to 2 by removing the new occurrence,
which is the author-side remedy; baseline expansion is maintainer-only and was
not taken.

Verification at 8e1b43c01

node scripts/pm/dispatch-gates.mjs with no paths (change set derived by the
script itself from the real diff vs merge base 09b880b0c; committed 2, working
tree 0, untracked 0) matched one family. The union below was re-run on the final
commit and each row quotes the gate's own verdict line, with exit codes captured
before any pipe:

GateExitVerdict line
check:role-word0check-role-word: OK, no new occurrences of the reserved word. (ledger: 43 baselined files, 129 occurrences; this file back to 2)
check:pm-governed-merges (matched)0check-governed-merges --self-test: 81 assertions …
check:skill-compatibility0check-skill-compatibility-version: 11 SKILL.md file(s) reconciled against 77 workspace packages
check:skill-frame-sync0check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
check:skill-frame-freshness0check-skill-frame-freshness: the decision frame in this tree is current with origin/main (fetched just now).
check:pm-skill-ratchet0check-skill-line-ratchet: AGENTS.md is 958 lines (ceiling 958; headroom 0).
check:pm-skill-id-lint0check-skill-id-lint: 15 file(s) clean (pattern /#[0-9]{3,}/g).
check:pm-governed-prose0check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces …
check:nul-bytes0check-nul-bytes: OK (scanned 6355 text file(s) … no raw ASCII control bytes).

check:role-word was not in the derived set and was added by hand after CI
found it. The cause is recorded rather than papered over: the gate's population
is ['content/docs', 'skills'], and the derivation extracts only
content/docs, scripts/role-word-baseline.json — a bare separator-less literal
like skills is refused as too generic by hintCovers, so the gate scores
silent for every skills/** card while genuinely covering it. Left for
separate triage; not touched here.

Residue derivation placed all 115 discovered families (1 matched, 35 undetermined,
79 silent). The published skills/ root carries no line ratchet, so this pays no
line budget.

Notes

  • Governed surface (skills/**) — this PR opens draft and stays draft. The
    maintainer merges; it is never queued, armed, or flipped ready. Review
    requested from os-zhuang. The second commit was added as a commit, never an
    amend or a force-push.
  • No changeset: skills/ appears in no package's published files list (checked
    across 73 package manifests, zero hits), so this PR releases nothing.
    skip-changeset applied, following the precedent of the most recent
    skills-only PR.
  • The strict-tier migration that would close the remaining strip-mode shapes is
    still open; the page says so without pinning a date.

Generated by Claude Code

…nt stripping
An unknown top-level `defineStack` key has thrown since the outermost strict
door closed; the published platform skill still taught that it was silently
stripped and therefore a harmless no-op. The direction of that error is the
harmful one: it ranks a stack that does not load as cosmetic.
Four sites corrected, all re-measured against the built `@objectstack/spec`:
- rules/bootstrap-patterns.md — the `driver:` mechanism sentence, the
"(Silently Stripped)" heading, and the inline "no-op" comment. The
prescription (drivers are plugins) is unchanged; only the stated mechanism
moves, and it now quotes the real load-time and TS2353 output.
- SKILL.md — the blanket "unknown keys are silently stripped" sentence is
replaced by the measured per-surface split (refused vs warned-then-dropped)
rather than flipped wholesale.
- SKILL.md — the feature-flags aside repeating the same stale claim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
claudeand others added 2 commits August 20, 2026 02:20
… refusal
The quoted defineStack refusal used the ADR-0090 D3 reserved word as its
example key, which added a third occurrence to a file baselined at two and
red-lit check:role-word. The sibling example in the sentence above it is
already `policies:`, so the quote now uses that instead.
Not an edited quote: the refusal was re-measured live against the built
@objectstack/spec with `policies` as the offending key, and the output is
reproduced as measured. The two pre-existing occurrences are untouched, and
the baseline is not modified — the ratchet returns to 2 by removing the new
occurrence, which is the author-side remedy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
@os-warren
os-warren added this pull request to the merge queueAug 20, 2026
Merged via the queue into main with commit 1800ffaAug 20, 2026
21 checks passed
@os-warren
os-warren deleted the claude/issue-9907-definestack-throws branch August 20, 2026 03:10
os-warren pushed a commit that referenced this pull request Aug 20, 2026
…ivation
check-role-word walks ROOTS = ['content/docs', 'skills'], but dispatch-gates
builds its watch hints from path literals carrying a separator, so the bare
word `skills` produced no hint at all: a skills-only card derived the content
half and scored this gate `silent`. PR #10038 paid one repair round for it —
a green local union, then `role-word count grew 2 to 3` in CI.
Adds the provenance-only subtree declaration (`skills/**`) in the gate's own
source, the same pattern root-file populations use, with the coupling to ROOTS
pinned in both self-tests. The extractor and hintCovers are untouched: that
refusal of bare single-segment literals is measured and deliberate.
No scanning behaviour changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] objectstack-platform skill still teaches that an unknown top-level defineStack key is silently stripped — #8687 made it throw

2 participants

@os-warren@claude