Skip to content

spec: the override declaration, the .custom pairing constants, the <base_ns>_custom namespace rule, the __c reservation, the overridable-type list and the base-field display whitelist #28404

Closed

Description

@objectstack-fleet

epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848

Filing gate: ④ child of epic #28236 (P1, cut after ADR-0146's rewrite merged in #28295) · the shared definitions N-09, N-10, N-13, N-14 and N-23a/b read. ⛔ Not a claim.

Part of #28236 (P1, child N-08) · governs: ADR-0146 D2 (declaration, type list), D3 (pairing, namespace, range), D6 (excluded types), D10 (__c, display whitelist) · lane: domain:spec · size: M.

Follows #28390 ruling A (6542177429): only the paired <base>.custom declares an override. The pair predicate below is what the gates read.

Business reason. A developer writing hotcrm.custom in a repository and an administrator customizing in Studio must produce the same declaration, one the write gate, the read, export and os validate all understand. Without one Zod shape and one set of constants, every door would spell "this item overrides HotCRM's" its own way.

Measured (75ecaf4ad8).

  • packages/spec/src has no overrides key, .custom constant or __c rule; no __c name appears in non-test sources under examples/ or packages/.
  • Every type D2 lists spreads MetadataProtectionFields (packages/spec/src/kernel/metadata-protection.zod.ts:81).
  • DEFAULT_METADATA_TYPE_REGISTRY (packages/spec/src/kernel/metadata-plugin.zod.ts) has 30 entries. Four runtime-creatable types appear on neither side of D2: seed, external_catalog, tool, skill.
  • packages/spec/src/kernel/namespace-prefix.ts:54#deriveNamespaceFromPackageId returns custom for hotcrm.custom. Namespaces must match /^[a-z][a-z0-9_]{1,19}$/ (namespace-prefix.ts:18#NAMESPACE_RE; the same regex inline at manifest.zod.ts:564), so <base_ns>_custom fits only a base namespace of 13 characters or fewer. The longest in the repository is feature_flags at 13 (packages/services/service-settings/src/manifests/feature-flags.manifest.ts:7).
  • Field names match /^[a-z_][a-z0-9_]*$/ (packages/spec/src/data/field.zod.ts:1363), which admits region__c. packages/spec/src/data/object.zod.ts:4709#ObjectExtensionSchema is strict and has no slot for patching a single field.

Wanted.

  1. An optional overrides: { package, basedOn } on every overridable type, one shared field set in the style of MetadataProtectionFields; its .describe() says basedOn is recorded and never judged (D2). The key survives defineStack(), the stored body and export.
  2. Pairing constants and pure helpers: the .custom suffix, customizationPackageId(baseId), isCustomizationPackage(id), the base of a pair, an "is the pair of" predicate, and the dependency range ^<major> from the base's manifest.version (D3; merge item 1). Dotted ids such as com.example.showcase.custom are covered. N-08 owns these; N-13 (the ensure door) and N-14 (uninstall protection) consume them and are Blocked-by this card. ⛔ Spelled nowhere else.
  3. Namespace rule, decided here: customizationNamespace(baseNs) returns <base_ns>_custom, never derived. A base namespace longer than 13 characters gets no pair namespace: the helper answers a named reason and N-13 refuses that base with a sentence. ⛔ No truncation, no hashing: either is a second spelling the declaration cannot be checked against. ADR-0146 is silent here, so the PR lists it for the maintainer. Nothing in the repository is affected today.
  4. The __c reservation: a suffix constant and isExtensionFieldName(name). Enforcement is N-23b.
  5. A closed list of overridable types, checked against the registry: permission, position, capability excluded; object extend-only; flow's and action's D9 conditions carried as data. The PR classifies seed, external_catalog, tool and skill, or reports to the maintainer the ones D2 cannot decide.
  6. The base-field display whitelist as one closed constant, each member admitted by D10's test against FieldSchema. The per-field patch slot goes on ObjectExtensionSchema here, or the PR says it rides N-23a.
  7. Generated baselines (authorable surface, JSON Schema, reference docs) regenerated; the Metadata-protocol level declared.

Tests. The schema accepts and refuses overrides shapes. Table tests cover the helpers (dotted ids, 13- and 14-character namespaces, the range). A type-list test pinned to the registry fails when an unclassified runtime-creatable type is added. A whitelist test checks every member exists on FieldSchema and none is structural.

Not here. Any door: the write gate (N-09), the read (N-10), the ensure door (N-13), uninstall (N-14), enforcing __c and the whitelist (N-23a/b).

Dedupe. Searched override declaration, __c suffix, custom namespace derived, display property whitelist: only #28236 and #28237 match.

Links back: #28236, #28390 (ruling 6542177429), ADR-0146 D2, D3, D6, D10, ADR-0048 §3.2.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V

    Claim: PM loop round 3 · 2026-10-31T14:36Z
    Session: session_017ihL2fi9zfJhUTubyJYb6V
    Account: marchtian
    Branch: claude/issue-28404-override-declaration
    Worktree: objectstack-issue-28404
    Domain: domain:spec
    Seat: domain:spec#4
    Ruling-ref: the maintainer put this card ahead of the queue (「28404 插队」, 2026-10-31T14:35Z); ADR-0146 (Accepted on main) D2, D3, D6, D10 and its "Decided here" items 1, 2 and 4; #28390 ruling A (6542177429): only the paired <base>.custom declares an override. The card's item 3 namespace rule (no pair namespace past 13 characters, no truncation, no hashing) and item 5's classification of seed, external_catalog, tool, skill are listed in the PR for the maintainer; the PR does not land before those are confirmed.
    File surface: packages/spec/src/kernel/metadata-protection.zod.ts or a sibling new module for the shared overrides field set and the pairing constants and helpers (the .custom suffix, customizationPackageId, isCustomizationPackage, the base of a pair, the pair predicate, the ^<major> range, customizationNamespace), the overridable-type list beside packages/spec/src/kernel/metadata-plugin.zod.ts's registry, the __c constant and isExtensionFieldName, the display-property whitelist beside packages/spec/src/data/field.zod.ts, the schemas of each D2 type that spreads MetadataProtectionFields only where the spread does not already carry the new set, packages/spec/src/data/object.zod.ts#ObjectExtensionSchema only if the per-field patch slot is taken here (else the PR says it rides N-23a), defineStack() only if the key would be dropped, the barrel exports, tests, regenerated spec artefacts and reference docs by repo tooling only, the ui/packages/types mirrors only where they are exhaustive copies, changeset (@objectstack/spec minor). No door: N-09, N-10, N-13, N-14, N-23a/b are not taken (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: TIER_DEFAULT (opus) under the quota-exhaustion exemption (maintainer 「你没有fable了,使用 opus开发完之后等契约复审」), effort: high
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: a developer's hotcrm.custom in a repository and an administrator's Studio customization produce the same override declaration | one Zod field set overrides: { package, basedOn }, one set of pairing constants and helpers, one overridable-type list, one __c rule and one display whitelist, spelled once in spec | every door of epic #28236 (N-09, N-10, N-13, N-14, N-23a/b) and os validate
    Thread-read: none
    Premise: holds on a8d2fd7f30: packages/spec/src has no overrides key, .custom constant or __c rule; deriveNamespaceFromPackageId('hotcrm.custom') returns custom (namespace-prefix.ts:54); ObjectExtensionSchema is strict with no per-field patch slot
    Serial constraints cleared: no open PR touches metadata-protection.zod.ts, namespace-prefix.ts, metadata-plugin.zod.ts, data/object.zod.ts, kernel/manifest.zod.ts or ADR-0146. #28390 (the ADR-0146 amendment recording ruling A, domain:skills, no PR yet) edits the ADR text only; this card reads the ruling, not that text

  2. added
    priority:p1High: required for production / M2
    and removed on Oct 31, 2026
  3. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
    os-dev-report

    {
      "issue": 28404,
      "status": "done",
      "branch": "claude/issue-28404-override-declaration",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28467",
      "head": "e0253ce45c42f6688e112e25a7eed70b16de77c7",
      "session": "session_017ihL2fi9zfJhUTubyJYb6V",
      "premise_still_valid": true,
      "gates": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths, from the merge base) on head e0253ce45c: 112 commands",
        "run": "112 run, 112 exit 0, 0 exit 3, 0 non-zero; exit codes recorded before any pipe",
        "ran_reconciliation": "dispatch-gates --ran: 112 derived accounted for, 112 run, 0 NOT-MEASURED (derived zero), 0 UNRUN; exit 0",
        "also_run": [
          "pnpm --filter @objectstack/spec check:generated :: exit 0 (14/14 current)",
          "packages/spec vitest run (full) :: exit 0, Test Files 865 passed, Tests 24491 passed, 1 todo",
          "pnpm --filter @objectstack/spec typecheck :: exit 0",
          "pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-export-override-declaration.test.ts :: exit 0, 3/3 (on 0f9936f445)",
          "pnpm --filter @objectstack/runtime typecheck :: exit 0 (on 0f9936f445)"
        ],
        "earlier_findings_fixed": [
          "check:llms-txt exit 1: kernel 30->31, total 212->213 (fixed in 395b5e16df)",
          "full spec suite: 3 files red on the new key (form reconciliation, flows translation face, file-description level-1 count), fixed in e8d402b7e9"
        ],
        "ci": "not cited: the PR is a draft just opened; the seat reads its check-runs"
      },
      "line_budget": {
        "size": "M",
        "total": "+1912 -21 over 88 files",
        "hand_written_src": "+641 (18 non-test .ts under packages/spec/src)",
        "tests": "+723 (6 test files, 4 new)",
        "generated": "the remainder (references, api-surface, export-origins, declaration-map, authorable-surface, skill indexes, state-count shards)"
      },
      "deviations": [
        "file surface widened, each by a gate or suite pin: 12 liveness ledgers (props.overrides planned rows), packages/spec/llms.txt counts, ledger rows in src/system/metadata-form-zod-reconciliation.test.ts (8 root omit rows) and src/system/flows-translation-face.test.ts (1 noReader row)",
        "runtime test added: packages/runtime/src/domains/packages-export-override-declaration.test.ts (the export survival proof)",
        "the module header of override-declaration.zod.ts opens at level 2 so the description renderer's level-1 count stands",
        "first merge of origin/main: its regeneration was amended into the merge commit to carry the trailer pair (os-regen hook accepted it); the two later merges kept merge then regeneration as separate commits; the third merge resolved a text conflict in src/ui/app.zod.ts (override spread placed inside main's new superRefine(checkAppLandingItems))",
        "Fable review not run, per the order",
        "pnpm install re-run in the worktree after a merge (main added @objectstack/types to connector-mcp; the stale link made check:type-check-debt refuse with exit 3)"
      ],
      "files_changed": {
        "count": 88,
        "hand_written": [
          ".changeset/28404-override-declaration.md",
          "packages/runtime/src/domains/packages-export-override-declaration.test.ts",
          "packages/spec/liveness/action.json",
          "packages/spec/liveness/app.json",
          "packages/spec/liveness/book.json",
          "packages/spec/liveness/dashboard.json",
          "packages/spec/liveness/dataset.json",
          "packages/spec/liveness/doc.json",
          "packages/spec/liveness/email_template.json",
          "packages/spec/liveness/flow.json",
          "packages/spec/liveness/mapping.json",
          "packages/spec/liveness/page.json",
          "packages/spec/liveness/report.json",
          "packages/spec/liveness/translation.json",
          "packages/spec/llms.txt",
          "packages/spec/src/automation/flow.zod.ts",
          "packages/spec/src/data/field-customization.test.ts",
          "packages/spec/src/data/field-customization.ts",
          "packages/spec/src/data/index.ts",
          "packages/spec/src/data/mapping.zod.ts",
          "packages/spec/src/kernel/customization-package.test.ts",
          "packages/spec/src/kernel/customization-package.ts",
          "packages/spec/src/kernel/index.ts",
          "packages/spec/src/kernel/override-declaration.test.ts",
          "packages/spec/src/kernel/override-declaration.zod.ts",
          "packages/spec/src/system/book.zod.ts",
          "packages/spec/src/system/doc.zod.ts",
          "packages/spec/src/system/email-template.zod.ts",
          "packages/spec/src/system/flows-translation-face.test.ts",
          "packages/spec/src/system/metadata-form-zod-reconciliation.test.ts",
          "packages/spec/src/system/translation.zod.ts",
          "packages/spec/src/ui/action.zod.ts",
          "packages/spec/src/ui/app.zod.ts",
          "packages/spec/src/ui/dashboard.zod.ts",
          "packages/spec/src/ui/dataset.zod.ts",
          "packages/spec/src/ui/page.zod.ts",
          "packages/spec/src/ui/report.zod.ts",
          "packages/spec/src/ui/view.zod.ts"
        ]
      },
      "mcp_calls": "0",
      "api_writes": "2 - relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41238481969 -> PR #28467); this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/28404/comments)",
      "open_questions": [],
      "for_the_maintainer": [
        "namespace: customizationNamespace answers <base_ns>_custom; a base namespace over 13 characters gets none (reason base_namespace_too_long), no truncation or hashing",
        "seed: excluded, basis undecided (D2 admits it, ADR-0126 section 3 calls it one-shot)",
        "datasource: excluded, basis undecided (ADR-0126 section 3 origin-gated; the card's census missed it)",
        "tool and skill: excluded, basis undecided (behavioural, no ledger switch or D9-style conditions)",
        "external_catalog: excluded, basis not_package_shipped (D2's first sentence), for confirmation",
        "prerelease base version: customizationDependencyRange('2.0.0-beta.1') answers ^2",
        "basedOn required (non-empty) whenever overrides is present"
      ],
      "out_of_scope_findings": [
        "class: b · reach: GET /packages/:id/export omits emailTemplates (assemblePackageManifest excludes it), so an overriding email template in <base>.custom does not travel · evidence: packages/runtime export assembly · dedupe words: export emailTemplates skipped, package export email template · carrier: N-10 · noted, not filed",
        "class: none (loader decision) · reach: defineStack views: (defineView containers) and translations: (locale bundles) cannot carry overrides; the key lives on the item shapes · evidence: override-declaration.test.ts strict defineStack covers 11 item collections, not these two · carrier: epic #28236 read and Studio children · noted, not filed",
        "class: none · reach: none today · a flow clone of an overriding .custom flow would copy overrides onto a new name · carrier: N-21 · noted, not filed",
        "class: none · the card's census of undecided types missed datasource (the registry's only multi-line entry) · carrier: maintainer (for_the_maintainer item) · noted, not filed"
      ]
    }

    Reported 2026-10-31T20:22Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V

    Maintainer ruling recorded · 2026-10-31T21:05Z

    田騏, 2026-10-31 20:44Z, verbatim: 「同意」 (project message cmsg_01VmuUhXU9SSUzqJYyXSmYSiMH79UZBdPt7vGoKukHYjwz). That is agreement to the full analysis of the seven "For the maintainer" points of PR #28467 (project file issue-28404/decision-analysis.md, sections 结论先行 and 对席位的落地备注).

    Binding, all seven confirmed as built:

    1. A base namespace over 13 characters gets no pair namespace: a named refusal (base_namespace_too_long), with no truncation and no hashing.
    2. seed is excluded.
    3. datasource is excluded.
    4. tool and skill are excluded.
    5. external_catalog is excluded.
    6. A prerelease base version gives ^<numeric major> (2.0.0-beta.1 → ^2).
    7. basedOn is required inside the block.

    Follow-ups agreed with it:

    PR #28467 lands through pm-dispatch after a contract-review PASS on its head and green CI.
    Thread-read: none

  5. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V

    Landed · 2026-10-31T23:31Z · PR #28467 was merged by the queue at 2026-10-31T23:30Z as ba0084e414. The card closed by Fixes, and this seat strips pm:dispatched in the same act.

    What landed (epic #28236 N-08), all in spec:

    • overrides: { package, basedOn } on the thirteen ADR-0146 D2 types. basedOn is the base package's manifest.version; it is recorded and never judged.
    • The .custom pairing helpers: customizationPackageId, customizationBasePackageId, isCustomizationPairOf, customizationNamespace (no pair namespace past 13 characters) and customizationDependencyRange (^<major>).
    • The closed OVERRIDE_TYPE_CLASSIFICATION, every row on a decided basis.
    • __c and BASE_FIELD_DISPLAY_WHITELIST.

    No door reads any of this yet: N-09, N-10, N-13, N-14 and N-23 consume the helpers.

    Record:

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:specenhancementNew feature or requestpriority: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