Skip to content

docs: metadata-lifecycle overlay-whitelist table contradicts the registry on flow — table says allowOrgOverride ✅, code and tests say ❌ #11664

Description

@yinlianghui

What

The "Overlay whitelist (shared-DB tenancy invariant)" table in content/docs/concepts/metadata-lifecycle.mdx lists:

| flow | ✅ | Per-org overlays are allowed for automation definitions. |

But the registry — which that very section declares to be the single machine-readable source ("The whitelist lives in one place: MetadataTypeRegistryEntry.allowOrgOverride") — says the opposite (packages/spec/src/kernel/metadata-plugin.zod.ts, flow entry):

{ type: 'flow', …, supportsOverlay: false, allowOrgOverride: false, allowRuntimeCreate: true, … }

The adjacent code comment documents the rationale (the #6190 phantom: an org-scoped flow overlay wrote successfully and lost its binding on the next cold start, because the loader cannot merge per-org flow overlays; rolling the flag back turned the silent phantom into a loud 403 not_overridable). Conformance tests pin the refusal: save-meta-response-conformance.test.ts ("allowOrgOverride: false for flow, so an org-scoped write is …") and publish-meta-response-conformance.test.ts ("org-scoped draft is refused").

Why it matters

The table tells an integrator that an org-scoped flow overlay will persist; in reality the write 403s. Anyone building on the docs row plans a customization path that does not exist.

Suggested fix

Found while fact-checking a promo video's on-screen claims against the runtime (video-studio #4). Filing as a finding — unassigned, not claimed.

Activity

  1. added theissue type on Aug 24, 2026
  2. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    Contributor

    Triage: lands in content/docs (metadata-lifecycle overlay-whitelist table) → domain:devx, type Bug, pm:queue. Rationale: the table contradicts the registry and its tests on flow — docs assert allowOrgOverride ✅ where code and tests enforce ❌. Docs follow code: correct the table to the enforced ❌ today.

    ⚠️ Awareness note, not a blocker: #11665 (org-tunable parameters for packaged flows) is in the decision box this round — if the maintainer ever rules org-level flow tunability in, the registry changes first and the table follows it again. That possibility does not license the table to keep asserting a capability the runtime refuses now. Cheap, dispatchable as-is; also check whether the table row cites a source anchor the docs-audit gate should have caught (if so, note it for the audit's blind-spot ledger rather than widening this PR).


    Generated by Claude Code

  3. self-assigned this
    on Aug 24, 2026
  4. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator

    Claim: domain:devx PM seat, session session_015ahemw8RcTgqtxrj15PEZx, branch claude/issue-11664-flow-overlay-row.

    Dispatching at opus. Tier derived live at dd4113ec0, not recalled: no path-derived mandate — the surface hits none of the 3 declared glob(s).

    Premise re-verified on today's main, with a positive control so the reading is a measurement:

    packages/spec/src/kernel/metadata-plugin.zod.ts:827
      { type: 'flow', … supportsOverlay: false, allowOrgOverride: false, allowRuntimeCreate: true, … }
    content/docs/concepts/metadata-lifecycle.mdx:111
      | `flow` | ✅ | Per-org overlays are allowed for automation definitions. |
    CTRL  allowOrgOverride: true  ×6   ·  false ×33     ← the probe is genuinely reading that field
    

    Direct contradiction confirmed. Opus rather than the floor because the ❌ has to preserve a two-tier fact — org overlay closed, allowRuntimeCreate still true (ADR-0070) — and a one-character flip is exactly the edit that flattens both tiers into one and makes the row wrong in the other direction.


    Generated by Claude Code

  5. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator
    {
      "issue": 11664,
      "status": "done",
      "branch": "claude/issue-11664-flow-overlay-row",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11750",
      "premise_still_valid": true,
      "summary": "The premise held and understated the defect: the overlay-whitelist table in content/docs/concepts/metadata-lifecycle.mdx diverged from DEFAULT_METADATA_TYPE_REGISTRY on FOUR types, not one. `flow` ✅→❌ (rolled back #6283), `permission` ✅→❌ and `position` ✅→❌ (rolled back #6483, 2026-08-08 ruling), and `translation` was ABSENT from the table while the registry marks it allowOrgOverride:true — a false negative, so the table was wrong in both directions at once. Both corrected ❌ rows preserve the two-tier fact (allowRuntimeCreate stays true) using the Rationale column, which four existing rows (object/field/datasource/job) already use for exactly that — no new table convention was invented. Registry and conformance tests untouched: allowOrgOverride:false for `flow` is the correct outcome of the rollback. Docs-only, 1 file, +5/-3. The table is hand-maintained — verified before editing (no generator writes the page, no DO NOT EDIT marker, no gate compares table to registry), so the fix belongs in the page and not in a generator.",
      "tests": "All at final commit 180a416ac with a clean tree (git status --porcelain = 0 lines). NON-VACUITY BOTH DIRECTIONS via an AST-based comparator (typescript AST over DEFAULT_METADATA_TYPE_REGISTRY, not regex — several entries are multi-line; joined against the parsed mdx rows): BEFORE (git show HEAD:...mdx) exit 1 'VERDICT: 4 DIVERGENCE(S)' listing flow/permission/position mismatched + translation absent; AFTER exit 0 'VERDICT: CONSISTENT', 27 registry types vs 13 table types, zero registry-✅ types absent. The BEFORE run is the positive control, so the clean AFTER reading is a measurement, not a dead probe. Registry CTRL matched the dispatch premise exactly (allowOrgOverride true×6/false×33 by grep line count; AST resolves that to 5 true entries — the 6th grep line is a code comment at L789). SECOND SOURCE: content/docs/permissions/authorization.mdx:236 already stated `permission` declares allowOrgOverride:false, and content/docs/automation/jobs.mdx:42 already carried the correct `flow` two-tier fact — the table contradicted sibling docs pages too, and those pages needed no change. GATES: union derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no hand-fed paths; script took its own change set — 17 families over 1 path), all re-run at 180a416ac, exit codes captured before any pipe (redirect-then-capture, never $? after a tail). All 17 + check:nul-bytes green. Quoted verdict lines the gates printed themselves: '✓ doc authoring guard: 389 files clean — no bare metadata literals.'; '✓ check-doc-frontmatter: 403 page(s) under content/docs parse with yaml@2.9.0 …'; '✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).'; '✓ variant/doc gate: 18 discriminated union(s) — 8 governed, 10 exempt.'; '✓ check-nul-bytes --self-test: 75 assertions over a temp git repo'. doc-formula-expressions and doc-security-posture were RED on first run (ERR_MODULE_NOT_FOUND on packages/lint/dist) — the new-worktree missing-build trap, not this change; green after building the @objectstack/lint and @objectstack/spec dependency closures under the shared lock ('os-verify-lock: VERDICT command-exit 0 · held the lock 136s (2m16s) · waited 35s'). Every pnpm --filter run echoed its script name, so no zero-match silent-green. Control-byte self-scan on the edited file: no match, with a positive control proving the pattern is live. NOT MEASURED: CI gate job conclusions (report is delivered at draft-PR time per the dispatch contract; no CI wait).",
      "open_questions": [
        {
          "question": "Should a gate assert the docs table against DEFAULT_METADATA_TYPE_REGISTRY? (Reporting only — not built, per dispatch instruction.)",
          "options": [
            "A — Add a check that parses the mdx table and the registry and fails on any divergence, including a registry-✅ type absent from the table.",
            "B — Leave it to the docs-accuracy audit and human review."
          ],
          "recommendation": "A, and it is cheap. The comparison is fully mechanical — the throwaway comparator used for this PR's non-vacuity evidence already implements it end to end (AST-parse the registry, parse the table, join on type name) and correctly returned 4 divergences on the pre-fix file and 0 after. The drift went undetected long enough to reach a promo-video fact-check, and three of the four wrong rows came from rollbacks (#6283, #6483) whose authors correctly updated the registry and its code comments but had no mechanism telling them a docs table mirrored the column. Note the gate must check BOTH directions — a row that contradicts the registry AND a registry-✅ type missing from the table; `translation` was only findable by the second check. Not filed, per your instruction to file it yourself."
        },
        {
          "question": "Your table-shape question: can the existing column semantics carry the two-tier distinction without inventing a convention?",
          "options": [
            "A — Yes, the Rationale column already carries it; no shape change.",
            "B — No, it needs a footnote or a fourth column (allowRuntimeCreate)."
          ],
          "recommendation": "A — and this needed no judgment call, because the table already does it in four places: the `object`, `field`, `datasource` and `job` rows all state their allowRuntimeCreate value inside Rationale (e.g. datasource: '(`allowRuntimeCreate: true` — the datasource wizard persists origin: runtime rows.)'). I followed that existing precedent verbatim for `flow` and `permission`/`position`, so nothing new was smuggled into the table's shape. If you would still prefer an explicit fourth column, that is a clean follow-up — but it is a change to all 13 rows, not something this card should have decided."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED, by instruction: the registry↔docs-table gate described in open_questions[0] — you said you would file it.",
        "No other out-of-scope defects found. The sibling docs pages that mention allowOrgOverride (permissions/authorization.mdx, permissions/capabilities.mdx, ai/agents.mdx, automation/jobs.mdx, api/declarative-endpoints.mdx, references/system/email-template.mdx) were swept and all already agree with the registry — metadata-lifecycle.mdx was the sole outlier."
      ]
    }

    Two procedural notes for the PM, not in the JSON:

    1. skip-changeset was applied via the read→union→whole-set fallback, declared as required. The additive POST /issues/11750/labels was unavailable from this seat (the raw curl was refused by the session's command classifier, and gh is not installed in this container), so I read the label set first (size/xs), wrote the union ["size/xs","skip-changeset"], then read back. Read-back: documentation, size/xs, skip-changeset — all three present, nothing clobbered (documentation was added by the auto-labeler after my write, additively). The label's own description confirms the choice: "PR has no user-facing published change; bypasses the changeset gate." Justified against the actual rule rather than assumed: pr-automation.yml's changeset-check has no path-based docs carve-out (its if exempts only the label and the changeset-release/main branch), and AGENTS.md:942 states "Pure bug fixes do not require a changeset." This PR touches one file under content/docs/ and zero package source, so it publishes nothing.

    2. No domain:* label was produced and the issue's assignee/claim were not touched, per dispatch.


    Generated by Claude Code


    Generated by Claude Code

  6. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator

    os-dev-report

    (Re-posting the machine-readable half: in the previous comment the <!-- marker --> was eaten by the GitHub body sanitizer after it landed, so that comment is invisible to a marker scan. Same JSON, byte-identical. The two procedural notes — the skip-changeset label fallback and its read-back — remain in that earlier comment.)

    {
      "issue": 11664,
      "status": "done",
      "branch": "claude/issue-11664-flow-overlay-row",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11750",
      "premise_still_valid": true,
      "summary": "The premise held and understated the defect: the overlay-whitelist table in content/docs/concepts/metadata-lifecycle.mdx diverged from DEFAULT_METADATA_TYPE_REGISTRY on FOUR types, not one. `flow` YES->NO (rolled back #6283), `permission` YES->NO and `position` YES->NO (rolled back #6483, 2026-08-08 ruling), and `translation` was ABSENT from the table while the registry marks it allowOrgOverride:true — a false negative, so the table was wrong in both directions at once. Both corrected NO rows preserve the two-tier fact (allowRuntimeCreate stays true) using the Rationale column, which four existing rows (object/field/datasource/job) already use for exactly that — no new table convention was invented. Registry and conformance tests untouched: allowOrgOverride:false for `flow` is the correct outcome of the rollback. Docs-only, 1 file, +5/-3. The table is hand-maintained — verified before editing (no generator writes the page, no DO NOT EDIT marker, no gate compares table to registry), so the fix belongs in the page and not in a generator.",
      "tests": "All at final commit 180a416ac with a clean tree (git status --porcelain = 0 lines). NON-VACUITY BOTH DIRECTIONS via an AST-based comparator (typescript AST over DEFAULT_METADATA_TYPE_REGISTRY, not regex — several entries are multi-line; joined against the parsed mdx rows): BEFORE (git show HEAD:...mdx) exit 1 'VERDICT: 4 DIVERGENCE(S)' listing flow/permission/position mismatched + translation absent; AFTER exit 0 'VERDICT: CONSISTENT', 27 registry types vs 13 table types, zero registry-YES types absent. The BEFORE run is the positive control, so the clean AFTER reading is a measurement, not a dead probe. Registry CTRL matched the dispatch premise exactly (allowOrgOverride true x6 / false x33 by grep line count; AST resolves that to 5 true entries — the 6th grep line is a code comment at L789). SECOND SOURCE: content/docs/permissions/authorization.mdx:236 already stated `permission` declares allowOrgOverride:false, and content/docs/automation/jobs.mdx:42 already carried the correct `flow` two-tier fact — the table contradicted sibling docs pages too, and those pages needed no change. GATES: union derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no hand-fed paths; script took its own change set — 17 families over 1 path), all re-run at 180a416ac, exit codes captured before any pipe (redirect-then-capture, never $? after a tail). All 17 + check:nul-bytes green. Quoted verdict lines the gates printed themselves: 'doc authoring guard: 389 files clean — no bare metadata literals.'; 'check-doc-frontmatter: 403 page(s) under content/docs parse with yaml@2.9.0'; 'docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).'; 'variant/doc gate: 18 discriminated union(s) — 8 governed, 10 exempt.'; 'check-nul-bytes --self-test: 75 assertions over a temp git repo'. doc-formula-expressions and doc-security-posture were RED on first run (ERR_MODULE_NOT_FOUND on packages/lint/dist) — the new-worktree missing-build trap, not this change; green after building the @objectstack/lint and @objectstack/spec dependency closures under the shared lock ('os-verify-lock: VERDICT command-exit 0 · held the lock 136s (2m16s) · waited 35s'). Every pnpm --filter run echoed its script name, so no zero-match silent-green. Control-byte self-scan on the edited file: no match, with a positive control proving the pattern is live. NOT MEASURED: CI gate job conclusions (report delivered at draft-PR time per the dispatch contract; no CI wait).",
      "open_questions": [
        {
          "question": "Should a gate assert the docs table against DEFAULT_METADATA_TYPE_REGISTRY? (Reporting only — not built, per dispatch instruction.)",
          "options": [
            "A — Add a check that parses the mdx table and the registry and fails on any divergence, including a registry-YES type absent from the table.",
            "B — Leave it to the docs-accuracy audit and human review."
          ],
          "recommendation": "A, and it is cheap. The comparison is fully mechanical — the throwaway comparator used for this PR's non-vacuity evidence already implements it end to end (AST-parse the registry, parse the table, join on type name) and correctly returned 4 divergences on the pre-fix file and 0 after. The drift went undetected long enough to reach a promo-video fact-check, and three of the four wrong rows came from rollbacks (#6283, #6483) whose authors correctly updated the registry and its code comments but had no mechanism telling them a docs table mirrored the column. Note the gate must check BOTH directions — a row that contradicts the registry AND a registry-YES type missing from the table; `translation` was only findable by the second check. Not filed, per your instruction to file it yourself."
        },
        {
          "question": "Your table-shape question: can the existing column semantics carry the two-tier distinction without inventing a convention?",
          "options": [
            "A — Yes, the Rationale column already carries it; no shape change.",
            "B — No, it needs a footnote or a fourth column (allowRuntimeCreate)."
          ],
          "recommendation": "A — and this needed no judgment call, because the table already does it in four places: the `object`, `field`, `datasource` and `job` rows all state their allowRuntimeCreate value inside Rationale (e.g. datasource: '(allowRuntimeCreate: true — the datasource wizard persists origin: runtime rows.)'). I followed that existing precedent verbatim for `flow` and `permission`/`position`, so nothing new was smuggled into the table's shape. If you would still prefer an explicit fourth column, that is a clean follow-up — but it is a change to all 13 rows, not something this card should have decided."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED, by instruction: the registry-vs-docs-table gate described in open_questions[0] — you said you would file it.",
        "No other out-of-scope defects found. The sibling docs pages that mention allowOrgOverride (permissions/authorization.mdx, permissions/capabilities.mdx, ai/agents.mdx, automation/jobs.mdx, api/declarative-endpoints.mdx, references/system/email-template.mdx) were swept and all already agree with the registry — metadata-lifecycle.mdx was the sole outlier."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions