Skip to content

os generate scaffolds fail the project's own gates: flow targets a nonexistent object, view is refused by os lint, action/app cannot target an existing object #21325

Description

@objectstack-fleet

QA-source: #21318 · path step ① (write metadata)

On a fresh npm create objectstack@17.6.0 tasks-app (namespace tasks_app) with objects tasks_app_project and tasks_app_task generated by os g object project / os g object task:

  1. npx os g flow task_done writes src/flows/task_done.flow.ts whose start node has config.objectName: 'tasks_app_task_done' — derived from the flow name, an object that does not exist — and prints ✓ Reaches the stack. os validate then exits 0 with only a warning: flow "task_done_flow" › start node: targets object 'tasks_app_task_done', which this stack does not define — if the name is wrong, the flow will never fire (and the runtime stays silent about it). The generator itself wrote the silent failure.
  2. npx os g view task writes a views container with top-level name, label, object and a list without label. os validate passes but flags "view tasks_app_task: sets name … no runtime effect (liveness: dead)" and the same for label; os lint exits 1: ✗ View "tasks_app_task" is missing a label required/label at views[0].list.label. The generated file's own comment says the server refuses a container whose name disagrees, while validate calls the key dead — the two cannot both be right.
  3. npx os g action complete_task and npx os g app tasks derive the target object from the NAME (tasks_app_complete_task, tasks_app_tasks) and are refused by the cross-reference check (Action 'complete_task' references object 'tasks_app_complete_task' which is not defined in objects / references flow 'complete_task_flow'), exit 1, nothing written. os g --help offers no flag to name an existing object, so the generator can only scaffold an action or app for an object that shares its name.

Expected

Every os generate output passes os validate AND os lint on the project it was generated into, and targets real metadata (or asks / takes --object). A generator that writes a never-firing flow or a lint-refused view teaches the AI author the wrong shape on its first step.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p1 · domain:cli · area:devpath · pm:queue. Every os generate kind emits a file that passes the project's own gates

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-02T04:57Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p1. NORTH-STAR path step ① is broken on a fresh 17.6.0 project. Three generators emit metadata that the project's own os validate / os lint refuse or warn on, and new authors and AI agents start from these.

    Direction.

    • flow, action and app take their target object from an argument, or from the stack's existing objects. ⛔ Never derived from the new item's name.
    • view emits the current shape the lint requires.
    • The pin is the family close-out: for every generator kind in the one roster, a fresh scaffold plus os g <kind> passes os validate, os build and os lint with zero findings. A new kind joins the pin automatically.

    Generated by Claude Code

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    priority:p1High: required for production / M2
    on Oct 2, 2026
  3. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p1, to triage's direction 5945870485. Landed as Fixes #21325.
    Session: session_01VvcEokUG1tvVxkceYfR5XB
    Account: huangyiirene
    Branch: claude/issue-21325-generate-scaffolds-pass-gates
    Worktree: objectstack-issue-21325
    Domain: domain:cli
    Seat: domain:cli#1
    File surface, derived at origin/main 222ecc27f9:

    • packages/cli/src/commands/generate.ts.
      • flow, action and app take their target object from an argument, or from the stack's existing objects. ⛔ It is never derived from the new item's name.
      • With no object named and none to choose, the generator refuses loudly and names the remedy. It ⛔ never writes a file that targets a missing object.
      • view emits the shape os lint requires. The dev settles the container name/label contradiction (the scaffold's comment says the server needs them; os validate calls them dead) from the code that reads them, and reports the finding with evidence.
    • The pin is the family close-out. The dev extends the existing roster pin packages/cli/test/generate-scaffold-validates.test.ts (or a sibling built on the same derived roster, GENERATOR_SCAFFOLD_TARGETS). ⛔ There is no second hand-kept list.
      • For every generator kind, a fresh scaffold plus os g of that kind passes os validate, os build and os lint with zero findings, warnings included.
      • A kind added later joins the pin with no edit.
      • The card's three cases are measured red before the fix.
    • Docs: the #### os generate entry under ### Scaffolding in content/docs/deployment/cli.mdx documents the new argument. That region only.
    • Changeset: one .changeset/21325-*.md for @objectstack/cli, at the level the real diff takes.
    • ⛔ packages/cli/README.md is cli README: documents -h / -v short flags that exit 2, and says there is no os plugin command group while os plugin build|sign|publish is registered #21310's surface (os-bill seat). If an os generate row there goes stale, the dev reports it rather than editing it.
      (stop on a breach outside these; explain in the report)
      Container & model: L, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable; the default).
      Clause-②: yes (widening). os generate accepts a new argument that names the target object. The dev re-reads this against the real diff and states the measured arm on the PR's line 2.
      Thread-read: 5945870485
      Serial constraints cleared: Of the 12 open PRs (all file lists read in this act), none touches generate.ts, the generate tests, cli.mdx or the CLI README. In-flight claims on the same area: #21310 (os-bill) owns packages/cli/README.md and the oclif help/version keys of packages/cli/package.json; #21308 (os-bill) owns the tsx reroute test, the root workspace files, patches/ and pnpm-lock.yaml; #21018 (this seat) is PR #21337, skills/ only. #21323 (this seat, claimed in the same act) owns the verify command, the shared author-time pipeline and a new os verify entry under ### Quality in cli.mdx. This card's cli.mdx region is the os generate entry under ### Scaffolding. All surfaces are disjoint. Whichever of the two PRs lands second merges origin/main before landing.
  4. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21325,
    "status": "done",
    "branch": "claude/issue-21325-generate-scaffolds-pass-gates",
    "pr": "#21369",
    "session": "session_01VvcEokUG1tvVxkceYfR5XB",
    "premise_still_valid": true,
    "summary": "All three of the card's cases were reproduced on 97239c3 (blank starter, tasks_app) and are fixed. view, flow, action and app now take every reference they write from an argument, or from the loaded stack's only object or flow. The new flags are --object, and --flow for action. They never derive a reference from the new item's name. They refuse, with the declared names listed and nothing written, when there is nothing to bind, when the choice is ambiguous, when a named reference does not exist, or when they run outside a project. --object and --flow on a type that takes neither are refused, not ignored. Each scaffold's own findings are removed: object drops its unconsumed description field; view drops the container name and label and gains list.label, with every field of the object as a column; flow is active; action gets locations; app's nav entry is labelled from the bound object. The pin is the family close-out: generate-scaffold-validates.test.ts (per PR, every kind, three doors, every severity) and the new nightly generate-scaffold-gates.e2e.test.ts (real commands on a fresh starter per kind). Both are derived from GENERATOR_SCAFFOLD_TARGETS and binds.",
    "tests": "All on HEAD de6107c, which merges origin/main f9bcd08; closure rebuilt after the merge (turbo build --filter=@objectstack/cli..., 59/59). (1) vitest --project unit, whole CLI: 245 files, 3485 tests passed. (2) --project integration on test/generate- and test/create-objectstack: 7 files, 67 passed, against a dist built from this head. (3) Nightly e2e (OS_TEST_TIERS=nightly): generate-scaffold-gates, generate-scaffolds-reach-stack, generate-object-namespace-prefix, generate-skill: 4 files, 69 passed. (4) pnpm --filter @objectstack/cli typecheck: exit 0, including check:test-typecheck; debt ledger unchanged. (5) pnpm lint (repo-wide, not narrowed): exit 0. (6) 95 dispatch-gates commands, all exit 0. Three first exited 3 (PREREQUISITE NOT MET: check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage) and were rerun green after their prerequisites were built. dispatch-gates --ran: 95 derived, 95 run, 0 NOT MEASURED, a derived zero. Ablations: see the ablations field. All 13 legs went red. Every restore was proven blob == HEAD with an empty git diff HEAD. E1 went through dist: ablation-dist-preflight showed the marker present, then absent after the restore build, and the tree clean.",
    "mcp_calls": "0. No MCP GitHub tool was called. One SendMessage reply to the PM probe, not a GitHub write.",
    "api_writes": "3, all through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft #21369, read back byte-identical, 18501 bytes); (2) label-write --assign huangyiirene on PR 21369 (read back: assignee huangyiirene; labels documentation, size/xl, tests and tooling, all applied by others); (3) this os-dev-report comment, POST /repos//issues/21325/comments. Plus git push to the branch, which is not a REST write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door, measured. Run npm create objectstack tasks-app (the in-repo on-ramp bin), then os g dashboard sales, then os validate. The result is exit 0 with a field-no-consumers warning on objects[0].fields.body, the starter's own tasks_app_note.body, which the author never touched. The same happens after any first view, flow, action, app or skill. os init -t app has the same shape: os g dashboard probe, then os lint, warns on my_app_item.description and my_app_item.status. Cause: the rule is silent while a stack holds no consumer root, so each fresh template carries a latent warning that the first consumer wakes. This blocks a literal zero findings on a fresh scaffold for 5 of 8 kinds. The new e2e pin records it as a one-entry shrink-only STARTER_LATENT_FINDINGS ledger; the entry must sit on a starter-declared object and still fire, so the pin goes red when the template is fixed. Owner: packages/create-objectstack/src/templates/blank/src/objects/note.object.ts and packages/cli/src/commands/init.ts TEMPLATES.app, outside this card's surface. Dedupe words: field-no-consumers starter template; note body latent warning; os init app template item description status; fresh scaffold consumer root",
    "carrier: #21310 (os-bill seat), packages/cli/README.md · noted, not filed. The README Quick Start runs os init my-app, then os generate object task, os generate view task, os generate flow task. The flow line is now refused, because the app template's object plus task makes two objects, so it needs --object task (for example os generate flow task_changed --object task). The Typical Workflow line os generate view customer still works.",
    "carrier: 承接者:无 · noted, not filed. packages/cli/bin/run-dev.js serves packages/cli/dist when dist exists. Measured: with src/commands/generate.ts mutated to status draft, tsx bin/run-dev.js g flow x_changed --dry-run printed status active from dist. Several CLI test headers say spawning run-dev.js keeps a suite independent of dist (generate-refuses-unimportable-alias, generate-skill.e2e, generate-refuses-name-outside-charset and others). In fact a spawned CLI pin judges build state, and an ablation of src alone stays green: this run's first E1 attempt did exactly that. It is test infrastructure with no public door, so per the filing rules it is recorded in the PR's Acceptance notes only.",
    "carrier: 承接者:无 · noted, not filed. The blank starter README (packages/create-objectstack/src/templates/blank/README.md line 43) says objectstack generate view NAME writes a view. It still does, but only when NAME is an object the stack declares, which the sentence does not say."
    ],
    "roster": {
    "method": "Fresh blank starter per leg, then os g of the kind, then validate, build and lint with --json; findings counted at every severity. Before: 97239c3 dist, os g KIND probe_thing (view: os g view note, the starter's object). After: de6107c; each leg first generates the prerequisites named by binds (object gate_target, then flow gate_target_changed), then the kind.",
    "object": "before: exit 0/0/0, 0 findings · after: 0 findings",
    "view": "before: exit 1/1/1 (list-view-field-unknown and sort-field-unknown errors on the fixed name column; lint required/label error at views[0].list.label; liveness-dead-property on name and label; starter note.body) · after: 0 findings beyond the starter ledger; os g view note leaves zero, ledger included",
    "action": "before: os g exit 1 (defineStack: object derived from the name, and flow NAME_flow) · after: 0 beyond the ledger",
    "flow": "before: exit 0/0/0 with 3 warnings at each gate (flow-trigger-unknown-object, flow-draft-status-ambiguous, starter note.body) · after: 0 beyond the ledger",
    "dashboard": "before: exit 0/0/0, 1 warning (starter note.body) · after: 0 beyond the ledger; already zero of its own, kept in the pin",
    "app": "before: os g exit 1 (object derived from the name) · after: 0 beyond the ledger",
    "skill": "before: exit 0/0/0, 1 warning (starter note.body) · after: 0 beyond the ledger; already zero of its own, kept in the pin",
    "picklist": "before: exit 0/0/0, 0 findings · after: 0 findings",
    "control": "bare starter, no os g: exit 0/0/0, 0 findings, before and after"
    },
    "view_name_label": "Both claims are true and they do not conflict. name: the boot registrar files a container under deriveViewContainerObject (packages/metadata/src/view-container.ts lines 77-83), which reads object first, then list.data.object and form.data.object, and only then name. With object set, name is never the key. Its only reader is viewContainerNameRefusal (packages/objectql/src/view-container-name-refusal.ts). That function refuses a name that is set AND differs from the key, and returns undefined when name is absent. So name can only restate the key or contradict it, which is the ledger's dead (packages/spec/liveness/view.json), and the refusal's own remedy says drop name. label: expandViewContainerWithDiagnostics (packages/spec/src/ui/view.zod.ts, around line 6830) gives each expanded ViewItem the label of its list or form entry, never the container's, and the ledger records no Studio reader. Verdict: both dropped. The view writes list.label, which os lint requires.",
    "argument_shape": "--object OBJECT, accepted on flow, action and app. The object is accepted as declared (tasks_app_task) or without the namespace prefix (task); the short form is looked up through the os g object prefix rule, never re-spelled. --flow FLOW is accepted on action. Both are long flags with a value, like the existing --dir, --output and --format. With no flag, the scaffold binds the stack's only object (or flow) and says so in the header. With none or several declared, the command refuses and lists them; it does not prompt, since no os generate path prompts. A view stays named after its object (os g view task); --object on view is refused with that explanation. Outside a project, any binding scaffold is refused. --object or --flow on a type that takes neither is refused. --flow is a second argument beyond the one the claim anticipated. It applies the same direction to the action's other reference, its flow, which was derived from the action's name (complete_task_flow): defineStack refused it when flows existed, and it loaded dangling when none did (measured on 97239c3: os g action approve exits 0, os validate exits 0, target approve_flow). I judged this an extension of the ruled direction, not a choice between two public shapes. The alternative was a script action with an invented body, a placeholder the existing skill scaffold comment calls worse than nothing.",
    "repro": {
    "setup": "In-repo on-ramp bin, then npm create objectstack tasks-app --skip-install (namespace tasks_app), os g object project, os g object task; in-repo CLI build",
    "before_97239c3c8a": "os g flow task_done exit 0, bound tasks_app_task_done · os g view task exit 0 · os g action complete_task exit 1 (object tasks_app_complete_task, flow complete_task_flow not defined) · os g app tasks exit 1 (object tasks_app_tasks) · os validate exit 0 with 7 warnings (flow-trigger-unknown-object, flow-draft-status-ambiguous, field-no-consumers on note.body, project.description and task.description, liveness-dead-property on view name and label) · os build exit 0 with the same 7 · os lint exit 1 (required/label at views[0].list.label, plus the 7 warnings)",
    "after_de6107c8e1": "os g flow task_done exit 1 (3 objects, asks for --object) · os g flow task_done --object task exit 0 · os g view task exit 0 · os g action complete_task exit 1 (asks for --object) · os g action complete_task --object task exit 0 (runs task_done_flow, the only flow) · os g app tasks exit 1 · os g app tasks --object task exit 0 · validate, build and lint each exit 0 with 1 warning: field-no-consumers at objects[0].fields.body, the starter's note object, which no os g wrote (see out_of_scope_findings)"
    },
    "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands at de6107c, change set from merge base f9bcd08: 95 commands, all exit 0 after three prerequisite builds. --ran with per-line exit codes: 95/95 run, 0 NOT MEASURED. pnpm lint full: exit 0. check:adr-0087-registration: 1 declared-breaking changeset with the disposition not-required (no-migration-prescription). check:changeset-no-major: no major; the level axis is not applicable locally (it is PR-scoped).",
    "deviations": [
    "Clause-② is measured as yes (narrowing), not the expected yes (widening). The flags widen; binding by name, binding outside a project, and an action with no declared flow are now refused, which is a narrowing of documented invocations. The changeset carries the BREAKING banner and an ADR-0087 disposition. The level is minor, under the launch-window convention.",
    "cli.mdx was edited outside the #### os generate region: the Quick Start 'Add more metadata' block and the Typical Workflow step os g flow opportunity. My change made both examples refuse, and the os-dev rule requires fixing published text a change falsifies. Neither block is in ### Quality (#21323's region).",
    "The pin's literal zero findings on a fresh scaffold is held per kind for the scaffold's own output. The starter template's own latent field-no-consumers is recorded in a one-entry shrink-only ledger in the e2e pin (and reported as a finding) because the template is outside the file surface. The per-PR in-process pin has no ledger, and asserts literal zero.",
    "Files beyond generate.ts: project-namespace.ts and scaffold-wiring.ts, the CLI internals generate.ts directly references, as the dispatch allows. Nine existing generate-family tests were updated because the binding change required it. Two new test files (generate-binds-from-stack.test.ts, generate-scaffold-gates.e2e.test.ts) and one helper (test/helpers/scaffold-bindings.ts) were added.",
    "Ablation attempts A3 and A10 were void the first time: ablation-replace refused them because the anchor or replacement counts did not move, so the test never ran. They were redone with fresh anchors. E1 was void the first time too: a false green, because the spawned CLI serves dist. It was redone as a dist ablation."
    ],
    "files_changed": [
    ".changeset/21325-generate-binds-from-stack.md",
    "content/docs/deployment/cli.mdx",
    "packages/cli/src/commands/generate.ts",
    "packages/cli/src/utils/project-namespace.ts",
    "packages/cli/src/utils/scaffold-wiring.ts",
    "packages/cli/test/generate-scaffold-validates.test.ts",
    "packages/cli/test/generate-scaffold-gates.e2e.test.ts (new)",
    "packages/cli/test/generate-binds-from-stack.test.ts (new)",
    "packages/cli/test/helpers/scaffold-bindings.ts (new)",
    "packages/cli/test/create-objectstack-stack-reach.test.ts",
    "packages/cli/test/generate-emission-parses.test.ts",
    "packages/cli/test/generate-object-namespace-prefix.test.ts",
    "packages/cli/test/generate-refuses-name-outside-charset.test.ts",
    "packages/cli/test/generate-refuses-unimportable-alias.test.ts",
    "packages/cli/test/generate-scaffold-wiring.test.ts",
    "packages/cli/test/generate-scaffolds-reach-stack.e2e.test.ts",
    "packages/cli/test/generate-stack-reach.test.ts",
    "packages/cli/test/scaffold-emission-typechecks.test.ts"
    ],
    "ablations": "Fix committed first. Each leg used scripts/ablation-replace.mjs in wrap mode inside an EXIT/INT/TERM trap script. Restore was proven on every leg: generate.ts blob 2c8b969c1f07 == HEAD, scaffold-wiring.ts blob 0d902b75dff7 == HEAD, git diff HEAD empty. Red counts: A1 flow binds the name-derived object, 4 red (flow and action legs, bound-object pin, start-node pin). A2 flow status draft, 2. A3 view container name and label again, 1. A4 view list.label removed, 1. A5 action locations removed, 1. A6 action object from its name, 2. A7 app nav object from its name, 2. A8 object description field again, 3 (flow, action and app legs). A9 resolver picks the first of several objects, 1. A10 resolver drops the prefixed --object lookup, 1. A11 unused --object/--flow ignored, 8. A12 reach reader identifies a views container by name only, 2. E1 (e2e, through dist: mutate, rebuild, preflight marker present, e2e, restore, rebuild, preflight --absent and tree clean), 6 (flow and action legs across validate, build and lint). A3 and A10 were each void once and redone with fresh anchors. E1's first src-only run stayed green (38/38) and is void.",
    "checks_after_push": "Read once on head de6107c, without waiting. 32 check runs: 10 success (Auto Label; Part-of PR must not also close its card; filter; No other open PR may claim the same single-writer path; Check PR Size; Check Documentation Links; The card this PR closes must claim this branch; Flag docs affected by code changes; Spec property liveness; No other open PR may claim the same issue), 2 skipped (Console Pin Gate; Packed-tarball smoke, opt-in), 19 in_progress, 1 queued. No failures at read time. CI convergence is in_progress."
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #21369 at c0190c97 (os generate binds every reference from an argument or the stack; each scaffold passes os validate, os build and os lint). Fixes #21325; landing through the queue

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-02T08:48Z


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21369 → 11905a4f8b (os generate binds declared metadata, and every scaffold passes os validate, os build and os lint). Fixes #21325: the card is complete

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-02T09:13Z


    Generated by Claude Code

  7. added a commit that references this issue on Oct 7, 2026
    11905a4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:clipriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions