Skip to content

docs(skills): objectstack-upgrade factual sweep — a phantom preflight key, an unsafe --from rule, a retired ETL target, and the withdrawn automatic-rewrite claim - #13861

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13840-skills-sweep-upgrade
Aug 31, 2026
Merged

docs(skills): objectstack-upgrade factual sweep — a phantom preflight key, an unsafe --from rule, a retired ETL target, and the withdrawn automatic-rewrite claim#13861
os-zhuang merged 1 commit into
mainfrom
claude/issue-13840-skills-sweep-upgrade

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #13840
Part of #13658

Flight ⑨ of the published-skills factual sweep. Target: skills/objectstack-upgrade/** — one file, SKILL.md, 696 lines at origin/main (PM premise re-derived from the fetched tree: holds, exactly one file, exactly 696 lines).

Governed surface — this PR stays DRAFT and is merged by the maintainer, by hand. No ready-flip, no queue, no auto-merge (Prime Directive #14).

Result

~150 behavioral claims inventoried · 5 FALSE (distinct facts) · 5 landing sites · ~40 NOT MEASURABLE recorded. Density 3.3% / 3.3% — inside the 1.5–10% working range set after flight ⑧, near its floor. skills/objectstack-upgrade is the healthiest surface swept since flight ②, which is itself the finding: the file's own design (a self-refreshing pinned-reading section, prescriptions quoted from the spec) is doing real work.

Every correction is byte-neutral-or-shrinking and lands in the same passage as the fact it fixes. Ceiling untouched.

The five corrections — 落点 | before | after

1 · §0 preflight source (1) — a phantom key, taught as the most authoritative reading

落点 line 108
before 1. **`manifest.protocol`** in the stack config (`'16.0.0'` → `--from 16`). This is the declared answer and the kernel checks it at load time.
after 1. **`manifest.engines.protocol`** in the stack config (`'16.0.0'` → `--from 16`). The declared answer, checked by the boot handshake.

manifest.protocol had zero occurrences in the whole repo outside this one line (grep -rn over *.ts/*.tsx/*.md/*.mdx/*.json, node_modules excluded — one hit, the skill itself). ManifestSchema (packages/spec/src/kernel/manifest.zod.ts) declares no protocol member, and it is not .strict(), so an author who took the advice literally and wrote the key would have it silently accepted and dropped. Nothing "checks it at load time" because nothing reads it.

The real key is manifest.engines.protocol (PluginEnginesSchema, manifest.zod.ts:70), and it really is checked at load: the ADR-0087 D1 handshake refuses boot via assertProtocolCompat (packages/runtime/src/app-plugin.ts) and assertArtifactProtocolCompatible (packages/runtime/src/artifact-reference.ts), raising OS_PROTOCOL_INCOMPATIBLE. Flight ① called this flavour phantom fields with zero repo-wide hits.

2 · §0 preflight source (3) — "the lower one is the safe --from" is unsafe, and the skill's own §3.3 says so

落点 lines 114-116
before If (1) and (2) disagree, the lower one is the safe --from — the chain is idempotent, so replaying a hop that has already been applied is a no-op, while skipping a hop loses its rewrites.
after If (1) and (2) disagree, settle it — ⛔ never default to the lower one: a default flip (3.3) stamps its constraint onto a source already past that major.

Idempotence holds for renames, not for the default-flip class — and this file already documents that class in §3.3, about the one conversion where it matters. field-required-notnull-explicit (packages/spec/src/conversions/registry.ts) fires on any field with required: true and no storage block. A protocol-17-authored field is exactly that shape, deliberately: post-17 required is the write contract only. The registry's own docblock states the hazard verbatim — "a loader that auto-applied this transform would stamp NOT NULL onto it, silently restoring the tri-binding the ADR removed".

Executed (PROBE 4, below): a 17-canonical source parses green and an understated --from 16 replay stamps storage: {notNull: true} onto it. Following the old advice adds NOT NULL DDL a customer deliberately did not ask for. Self-contradiction resolved in favour of §3.3.

3 · §2.3 target table row 1 — an enumeration that stopped growing when the schema did

落点 line 308 (was 305)
before a flat string enum (`none`/`constant`/`map`/`split`/`join`/`lookup`) with settings in `params`, executed row by row by the REST import path.
after a string enum, settings in `params`; the REST import path runs `none`/`constant`/`map`/`split`/`join`, passes `lookup` to reference resolution, rejects `javascript` (400).

The standing probe class from flight ⑧, hit on the first try. TransformType (packages/spec/src/data/mapping.zod.ts) has seven members; the skill presented six. Executed (PROBE 6): ["none","constant","lookup","split","join","javascript","map"].

Both halves of the row were wrong, in opposite directions:

  • javascript is a live schema member the doc omitted — and the REST import path rejects it with HTTP 400 UNSUPPORTED_TRANSFORM (packages/rest/src/import-mapping.ts), the deliberate implement-or-reject-loudly posture. An agent that never learns the member exists cannot read that 400.
  • lookup is not "executed row by row" — the module contract says the value is copied through, and the import pipeline's own reference resolution turns lookup names into ids, "so a dedicated re-implementation here would be a second dialect".

The spec's own tombstone prescription (packages/spec/src/shared/mapping.zod.ts) states both facts correctly, including the 400. The skill had quoted half of it. No spec-side twin to file — the oracle is intact; the copy drifted.

4 · §2.3 target table row 2 — a retired surface offered as a v17 home

落点 line 309 (was 306) + the lead-in at 303
before The prescription names two live targets … and the row multi-source, multi-stage transformationan **ETL transformation step**.
after The prescription names one live target; the rest is the business decision: and the row → **nothing** — the L2 ETL layer retired at 17, unexecuted. Do it where it runs: warehouse ELT, a flow, a job.

The sharpest one. An upgrade skill for protocol 17 pointed a customer at a surface that was retired in protocol 17. #6414 retired the whole L2 ETL layer — "automation/etl.zod.ts had no executor, and the sync architecture doc was recommending it". packages/spec/src/automation/etl.zod.ts no longer exists; all nine ETL defs sit in RETIRED_DEFS_BY_MAJOR[17]; executed, @objectstack/spec/automation exports zero ETL* names.

The replacement text is not invented — it is the CHANGELOG's own FROM → TO row for exactly this intent: "nothing. There is no replacement because there was never an implementation … Do the work where it runs (the destination warehouse's ELT, a flow, a scheduled job)."

And the real tombstone names one live target, not two — the import mapping. The second row was never in the prescription.

This exact defect has been paid down once already, in the spec: "fix(spec): the retryDelayMs tombstone no longer points an upgrading author at the retired ETL surface (#6630)". The published skill was the copy nobody re-checked.

5 · §3.5 worked rejection — the withdrawn automatic-rewrite claim, republished

落点 line 533 (was 536)
before Run `os migrate meta --from 16` to rewrite it automatically.
after Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand.

The sample "what a tombstone looks like when it works" quoted a sentence the spec had already withdrawn. The real prescription (packages/spec/src/shared/mapping.zod.ts, reproduced verbatim by PROBE 5) ends with the house form. retired-key.ts's docblock says why: the in-place AST codemod is separately commissioned, and "porting the listed edits into the project's own .ts sources is the author's work".

It also contradicted this skill's own §1 headline — "⚠ The one fact that surprises every operator: os migrate meta does not rewrite your source files."

packages/spec/src/shared/retired-key-migrate-sentence.test.ts pins against this claim class-wide via its WITHDRAWN_CLAIM regex, which matches the removed text verbatim. It never fired: that test's markdown corpus is one hard-coded path, the internal .claude/skills/spec-property-retirement/SKILL.md. Carded as #13859.

Two trims that pay for the above (net −3 lines, token-neutral)

The file sat at its ratchet ceiling with headroom 0, so every added word was funded by a deletion in the same passage. Both trims are inside the §2.3 block the corrections rewrite, and neither removes a fact:

落点 before after
lines 298-300 …**no runtime ever executed any of them**, so nothing is lost by deleting the key — but the customer wrote it because they wanted a transformation, and that need is real even though the key never served it. …**no runtime ever executed any of them**. The customer wrote it because they wanted a transformation, and that need is real regardless.
lines 310-313 That third row is not a joke and it is frequently the truth: … Whether the downstream data is therefore wrong is a question only the owner can answer, and it is exactly the kind of finding the report exists to surface. That third row is frequently the truth: … Whether the downstream data is wrong is a question only the owner can answer — exactly the kind of finding the report exists to surface.

Zero out-of-list changes. The diff is 15 insertions / 18 deletions in one file.

The two skills/** readings

reading before after delta
file skills/objectstack-upgrade/SKILL.md — lines 696 693 −3
file — tokens (check-skills-token-ratchet) 8333 (ceiling 8333, headroom 0) 8333 (ceiling 8333, headroom 0) 0
package skills/objectstack-upgrade/ — lines 696 693 −3
package — tokens 8333 8333 0

The package is one file, so the two readings coincide. Net line budget ≤ 0: satisfied (−3). Ceiling unchanged — no raise requested, none needed.

Executed evidence (non-vacuity control)

Probes ran against a freshly built @objectstack/spec (pnpm --filter '@objectstack/spec^...' build && pnpm --filter @objectstack/spec build, VERDICT command-exit 0 under scripts/pm/os-verify-lock.sh), importing the built dist/ — not source.

PROBE 3 — §3.3's headline claim, proven true by execution. The skill says the 16 → 17 crossing has exactly one migration-chain-only conversion: retired from the load path and leaving nothing to reject it, so validate stays green while the chain still has work. Classifying that class needs a parse, not a grep — 46 of the 58 major-17 conversions are retiredFromLoadPath, and most of them ARE rejected by a tombstone. Driving each conversion's own fixture.before through ObjectStackDefinitionSchema.safeParse:

conversions toMajor 17: 58
of those retiredFromLoadPath: 46
OLD SHAPE PARSES GREEN (the skill's migration-chain-only class): 1
  - field-required-notnull-explicit

Exactly one. VERIFIED, and the count is right for the right reason.

PROBE 4 — the same conversion, and the evidence for correction 2.

applied: 1 · {"conversionId":"field-required-notnull-explicit",
  "from":"required: true (implied NOT NULL)","to":"storage.notNull: true",
  "path":"objects[0].fields.name.storage.notNull"}
resulting field: {"type":"text","label":"Name","required":true,"storage":{"notNull":true}}
PRE-migration source parses green (validate would be green): true

Both halves of §3.3 confirmed — green validate with chain work outstanding — and the stamping hazard of an understated --from demonstrated in the same run.

PROBE 5 — the real rejection, verbatim. code: invalid_type, expected: "never", path connectors.0.fieldMappings.0.transform, message = the tombstone's own prescription ending "…to list the mechanical edits for existing sources; apply them by hand." Confirms the §3.5 sample's shape (invalid_type, expected: never, fix-it text as the error — VERIFIED) and its ending (FALSE, correction 5).

PROBE 6 — TransformType options ["none","constant","lookup","split","join","javascript","map"] (correction 3).

The skill's own refresh command, run verbatim (§"How this table is refreshed"): 17.0.0 floor 10 | 16->17: 58 converted, 77 semantic. It works exactly as written, against the installed package, with no edit — VERIFIED.

The chain, executed end to end on a protocol-16 source: field-conditionalRequired-to-requiredWhen rewrote conditionalRequiredrequiredWhen value-unchanged, and agent-tools-to-skills removed the inline tool list. Both are the shapes the file's os:check block teaches as v17-canonical — VERIFIED.

Verified, worth recording

  • The pinned-reading section is doing its job, and is not a falsehood. §"The v17 prescription set, as of 17.0.0-rc.5" is a deliberately bounded snapshot with a stated refresh procedure and a stated tie-break ("the install wins"). Re-measured at @objectstack/spec 17.2.0 — four publishes past the pin — every number has grown, which is the direction the design anticipates: D2 conversions 45 → 58, D3 semantic entries 29 → 77, retiredKey() tombstones 113/32 files → 179/44 files, distinct [REMOVED] prescriptions 96 → 145. Support floor still 10; PROTOCOL_VERSION still 17.0.0. Left unedited on purpose — re-pinning is a maintainer call, not a sweep correction, and the deltas are recorded here so they stay legible.
  • One nuance inside that section, recorded rather than corrected: RETIRED_KEYS_BY_MAJOR[17] no longer contains data/ExternalFieldMapping:transform#8075 retired the whole external-lookup family and the def retirement subsumed the key entry. That is a removal, the one direction the pin's contract does not describe ("registrations after that publish add entries"). It was correct at rc.5 (the retirement landed in 17.0.0 GA, after rc.6), so it is a stale pin, not a false claim. Flagged for whoever re-pins.
  • applyConversionsToStoredItem really is called by both seams the skill names — packages/metadata/src/loaders/database-loader.ts and packages/objectql/src/plugin.ts — and really does pin includeRetired: true. One narrowing the skill does not state: flow rows are skipped at those two seams and canonicalize at registerFlow instead, "with the same full-chain policy", so the conclusion the skill draws is true for every row type. Recorded, not corrected — no reader action changes and the budget was spent on facts that do.
  • STORED_ONLY_FLAGS in packages/cli/src/commands/migrate/meta.ts is exactly apply, yes, force, type, database-url — the Failure-modes row matches member for member, and the mutual exclusion really is refused rather than ignored, in both directions.
  • Every printed string the skill quotes is real: Applied N mechanical change(s), N manual change(s) require your judgment, Migrated stack is schema-valid, Nothing to migrate, the path: from → to (conversionId) line, and the why: / verify: labels.
  • The published-file-list claim holds: packages/spec/package.json files is dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json — so the conversion/migration registries really are not shipped and src/**/*.zod.ts really is the only src that is, exactly as §2.1's callout says.
  • MigrationFloorError's message matches the Failure-modes row; os validate really carries a positional config arg and --strict, and really runs a schema pass plus an authoring-rule pass.

NOT MEASURABLE (~40, recorded, never skipped)

  • ~20 process claims — the .upgrade/ workspace convention, the decide-alone-or-ask rules, the "how to ask" shape, the report template's five sections. Judgment prescriptions with no implementation to read.
  • ~8 pinned rc.5 readings — measurable only against a publish this shallow checkout cannot reach. Re-measured at 17.2.0 instead and recorded above.
  • ~10 cross-package runtime claims — the stored --apply write path (needs a booted deployment DB), os migrate files-to-references / os migrate value-shapes enforcement effects (need a deployment each), the R2 renamed-context-field trap (needs a real dispatch). This is the class the flight-① addendum sized at ~20 min/claim and the roster now tracks for a dedicated harness flight.
  • check:skill-examples population: this package has exactly 1 os:check marker, so its population is one fence — non-empty, control not needed (contrast flight ③'s empty-population rule). The block type-checks and its two taught renames were confirmed by execution above.

Out-of-scope findings, filed unassigned

Neither is touched here.

Gates

Derived from the real diff via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (change set read by the script itself from the merge base; not a hand-built list) — 14 families, all re-run on the final commit 414cbd368 with a clean tree. Exit codes captured before any pipe.

0 | pnpm check:agent-test-spelling
0 | pnpm check:corpus-claim-drift
0 | pnpm check:cross-package-test-inputs
0 | pnpm check:doc-authoring
0 | pnpm --filter @objectstack/lint run check:doc-formula-expressions
0 | pnpm check:pm-governed-merges
0 | pnpm check:role-word
0 | pnpm check:skill-compatibility
0 | pnpm check:skill-frame-sync
0 | node scripts/check-ci-filter-parity.mjs
0 | node scripts/check-cross-package-test-inputs.mjs
0 | node scripts/check-shard-attestation.mjs
0 | node scripts/check-skills-token-ratchet.mjs
0 | node scripts/check-nul-bytes.mjs          (standing rule, not path-derived)

check-skills-token-ratchet prints the load-bearing line: skills/objectstack-upgrade/SKILL.md is 8333 tokens (ceiling 8333; headroom 0).

Two families needed a note rather than a verdict:

  • node scripts/check-test-completeness.mjs — NOT MEASURED, by the gate's own instruction. It grades a saved turbo run test log and the derived family names it with no argument; it exits 3 (deliberately distinct from a finding's 1) printing "the local reading for this gate is NOT MEASURED … It is not a red, and there is nothing here to fix." CI tees the log and passes it.
  • check:doc-formula-expressions first exited 1 with PREREQUISITE NOT MET ("Nothing was measured … It is NOT a finding") because @objectstack/formula and @objectstack/lint were unbuilt in a fresh worktree. Built both under the verify lock, then it ran and passed — the 0 above is the real measurement.

skip-changeset verified rather than assumed: the diff is a single skills/** markdown file and publishes nothing from any package.

Posture

Draft, human merge, needs:contract-review attached to this PR and to card #13840 in the same stroke (批 #12 CONTENT limb) — ⛔ not self-cleared. Sibling flight ⑩ (#13841, skills/objectstack-ai/**) ran this same wave on a disjoint face; no file overlap.

Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT

Generated by Claude Code


Generated by Claude Code

…cted

Flight ⑨ of the published-skills factual sweep (#13658). Five behavioral
claims verified FALSE against the implementation and corrected in place;
net -3 lines, token-neutral at the ratchet ceiling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@huangyiirene huangyiirene added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation labels Aug 31, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Contract review — PASS at head 414cbd368

Applicability: clause-② CONTENT limb (批 #12). Both carriers attached same-stroke at creation, read back; cleared in this stroke.

Reviewer qualification, machine-read this sitting: get_sessionexternal_metadata.last_served_model = claude-fable-5 = CONTRACT_REVIEW_TIER.

Review: report + diff read; anchors independently re-measured on origin/main this hour —

Executed evidence is the strongest of the program: probe 3 PARSES all 58 major-17 conversions' before-fixtures to prove exactly one old shape still validates green (the headline 3.3 claim true for the right reason), and probe 4 runs that conversion end-to-end demonstrating correction 2's hazard in one run. The rc.5 pin section deliberately left un-repinned (bounded snapshot with a stated tie-break — growth is its design) is the right restraint; the re-measured 17.2.0 figures are recorded in the report for whenever the maintainer chooses to re-pin. #13860 (manifest.specVersion advisory unreachable for stack configs) correctly filed for the maintainer's decision, not ruled by anyone here.

Budget: net −3 lines, token-neutral at a zero-headroom ceiling, trims declared in-list. needs:contract-review cleared on BOTH carriers. Governed .md: PR remains DRAFT for the maintainer's merge (os-zhuang / hotlong requested).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills-sweep ⑨: objectstack-upgrade (696 lines, 1 file) — behavioral-claim verification, content-class execution-first

3 participants