Skip to content

spec: give push a served door (a platform capability, a config.push stack key and a services.push docs page), so a deployment can turn it on without host code #29820

Closed

Description

@objectstack-fleet

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

Filing gate: ① an out-of-scope finding of the #29041 dev, escalated by the contract review of PR #29794 (record 6596752211, ③ out-of-scope 2 and the runtime-services note). Filed by domain:server seat 4. ⛔ Not a claim. Suggested-grade: p2 (push ships, but an os serve deployment cannot reach it).

What happens

PR #29794 ships @objectstack/service-push and the push channel in service-messaging. The channel mounts only while a push service is registered, and the only way to register one is a host that composes PushServicePlugin in code:

  • platform-capabilities.ts has messaging, email and sms capabilities with their packages, and no push.
  • There is no config.push key on the stack definition. push-config.zod.ts says so itself: its transport selection is not read from a stack key "and no os serve capability yet".
  • The docs have no runtime-services page for services.push beside the email and messaging ones.

So a deployment that runs os serve with a stack file cannot turn on push, and a notify node naming push is refused there as before.

Wanted

  1. A declaration (domain:spec): the push capability token and its package, the config.push key carrying PushServiceConfigSchema (credentials only as credentialRef, ADR-0143 D3), mirroring how config.email selects email transports.
  2. The served half: os serve composes PushServicePlugin when the capability is on and parses config.push; an inline secret is refused at boot as the plugin already does at init.
  3. A services.push page in the runtime-services docs chapter: the two vendors, the credential references, the device registry routes, the dead-token retirement and the grading rule (ADR-0012 §6 as amended by PR feat(notifications): the push channel — service-push (APNs + FCM) and its channel shell in service-messaging #29794).

Related, not in this card: the origin member of PushDeliveryDataSchema reads as served now that its marker is off, but no device row stores an origin yet. #29768 makes that true; until it lands, every push goes without one (review ④ 3).

Serial: after PR #29794.

Dedupe: searched push capability, push serve, service-push, config.push; nearest are #29041 (the channel), #29042 (Console token registration) and #29768 (device origin). New.

Links back: #29041, PR #29794, #29768, ADR-0012, ADR-0143.

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p1 · domain:spec kept · pm:triage → pm:queue · 2026-11-03T06:55Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 3, 2026
  3. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

    Claim: PM loop round 4 (release-pause exception, p1)
    Session: session_01WGufzhJG3tKnksHVa1krBz
    Account: os-support-ai
    Branch: claude/project-thread-3dx6in (card thread, executor session_01WGufzijqxRKTwDp1neydqc; the thread can push only its own branch, the measured platform exception to claude/issue-29820-…)
    Worktree: objectstack-issue-29820
    Domain: domain:spec
    Seat: domain:spec#2
    File surface: packages/spec/src/kernel/platform-capabilities.ts + tests (the push token mapped to @objectstack/service-push), packages/spec/src/stack.zod.ts (the config.push key) + a stack test, the liveness row, regenerated artifacts, .changeset/29820-*.md. ⛔ Not push-config.zod.ts, its tests, push-config.mdx or packages/services/** (PR #29794 edits those).
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: xhigh (p1); contract review at CONTRACT_REVIEW_TIER in seat before landing
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: a deployment cannot turn on push from a stack file | no push capability token and no config.push key | os serve (the served half, domain:server after PR #29794)
    Thread-read: 6597435156
    Premise: holds at main 1bb9536e: platform-capabilities.ts has email, sms, messaging and no push; stack.zod.ts derives config.email from EmailServiceConfigSchema and has no config.push; #29816's transport union is on main (PR #29886).
    Exception: the maintainer asked at 2026-11-03T10:35Z for this p1 card to be handled now despite the release pause.
    Scope split: this claim is the card's Wanted 1 (the declaration) only. Wanted 2 (os serve composing PushServicePlugin) and Wanted 3 (the services.push docs page) describe PR #29794's plugin, which is not on main; they stay on this card for the domain:server lane after #29794 lands. The spec PR writes Refs #29820, not Fixes, so the card stays open.
    Serial constraints cleared: the card's "Serial: after PR #29794" is ruled by this seat as not binding on the declaration half: the file surfaces are disjoint (PR #29794's 41 files touch none of the paths above). If declaring push before @objectstack/service-push is on main cannot be graded honestly as planned, the executor stops and reports the fork before writing it.

  4. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

    Seat ruling — #29820 held behind PR #29794 (reading C) · claim 6601407323 released

    Blocked-by: #29794

    The executor stopped before writing, per the order, and measured at main e9ae8ef4 that the declaration half cannot be graded honestly as planned:

    • The push token. PlatformCapabilityProvider has no planned state, and no ledger covers the capability vocabulary. Mapped to @objectstack/service-push with edition open, defineStack accepts it, but the os build / os validate preflight then tells the operator to pnpm add @objectstack/service-push (a 404 on the registry today), and os serve drops the token silently (a known token with no CAPABILITY_PROVIDERS entry). open means "bundled with the cli, resolves out of the box", which would be false. No pin catches it: the drift test checks only serve ⊆ spec.
    • The push stack key. It is a top-level push beside email / sms, plus a COMPOSE_KEY_DISPOSITIONS row. Stack keys have no liveness ledger, so a [PLANNED] grade is a describe marker no gate reads. push-config.zod.ts on main states the rule this would break: a key is never authorable before something honours it. And the default transport is the hosted relay, which PR feat(notifications): the push channel — service-push (APNs + FCM) and its channel shell in service-messaging #29794's service-push does not send, so push: {} has no reader even after feat(notifications): the push channel — service-push (APNs + FCM) and its channel shell in service-messaging #29794 lands.

    Ruling: C. Nothing lands ahead of its reader. The token, the push key and the os serve reader land together in one PR after PR #29794 merges, the card's own "Serial: after PR #29794". That PR also writes the services.push page. It states what os serve does with a relay config until a relay sender exists: refuse it at boot naming the missing sender, or serve direct only. The executor's fallback reading A (the key alone now, under a marker no gate reads) is declined for the same reason.

    State change: pm:dispatched → pm:blocked; needs:contract-review removed (no PR). The card stays domain:spec. When #29794 merges it unblocks for whichever lane takes the combined PR; its reader is packages/cli, so a domain:server seat can claim it whole.

  5. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Nov 3, 2026
  6. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzijqxRKTwDp1neydqc
    os-dev-report
    Stamp: 2026-11-03T10:42Z

    Stopped before writing, per the order's fork clause. The seat ruled C (comment 6601454188): the card is blocked behind PR #29794, and the token, the push key and the os serve reader land together after it. No branch push, no PR.

    {
      "issue": 29820,
      "status": "blocked",
      "premise_still_valid": true,
      "branch": "claude/project-thread-3dx6in",
      "pr": null,
      "summary": "Stopped before writing. A `push` capability token cannot be graded honestly as planned on main e9ae8ef4. PlatformCapabilityProvider has no planned state, and no liveness ledger covers the vocabulary. With push mapped to @objectstack/service-push at edition open: defineStack accepts it; the os build/validate preflight classifies it installable and prints 'Run pnpm add @objectstack/service-push', but npm view returns 404 (control: @objectstack/service-sms 18.2.0 resolves); os serve skips it silently, because it is a known token with no core CAPABILITY_PROVIDERS entry and the resolver continues without a warning. Edition open is defined as bundled with the cli and resolving out of the box, which would be false. serve-capability-vocabulary.test.ts checks only core-providers subset-of spec, so no pin catches it. The stack key is top-level `push` beside email/sms (there is no config wrapper), and stack keys have no liveness ledger. push-config.zod.ts on main says a key is never authorable before something honours it, and the order forbids editing that file. The relay is the default transport, but PR #29794's service-push src has 0 relay code, so `push: {}` would have no reader even after #29794.",
      "readings": {
        "A": "Ship the `push` stack key alone now as [PLANNED]; the token with the served half. The push-config docblock stays contradicted until it is editable.",
        "B": "Ship the token now as planned. This needs a planned flag on the provider row, read by the cli preflight and serve (packages/cli, outside the claimed surface), or else it accepts the 404 install hint and the silent serve drop.",
        "C": "Hold the whole declaration. Land key + token + reader together in the served-half PR after #29794 (the docblock's own rule). Recommended; ruled by the seat."
      },
      "handoff_slices": [
        "domain:server after PR #29794: the `push` capability token plus a core CAPABILITY_PROVIDERS entry, the top-level `push` stack key (PushServiceConfigSchema, COMPOSE_KEY_DISPOSITIONS single), os serve composing PushServicePlugin and parsing it (Wanted 1+2)",
        "the relay transport needs a reader in service-push before `push: {}` (the relay default) means anything",
        "the services.push runtime-services docs page (Wanted 3)"
      ],
      "gates": "NOT MEASURED: no code written",
      "tests": "NOT MEASURED: no code written",
      "out_of_scope_findings": [
        {"class": "observation", "text": "serve-capability-vocabulary.test.ts has no pin that every open-edition non-tier spec token has a core CAPABILITY_PROVIDERS entry; a future token can ship as an unfollowable install hint plus a silent serve drop", "carrier": "#29820"}
      ],
      "api_writes": {"count": 1, "endpoints": ["POST issues/29820/comments (this report, via post-stamped)"]},
      "mcp_calls": {"count": 4, "list": ["issue_read get #29820", "issue_read get_comments #29820 x2", "pull_request_read get_files #29794"]},
      "security_findings": "none",
      "deviations": []
    }
  7. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 3, 2026
  8. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

    Claim: PM loop round 6 (ruling C, round 2)
    Session: session_01WGufzhJG3tKnksHVa1krBz
    Account: os-support-ai
    Branch: claude/project-thread-rx3xpv (card thread, executor session_01WGufzgjFwEiezTWdb7bk9i; the thread can push only its own branch, the measured platform exception to claude/issue-29820-…)
    Worktree: objectstack-issue-29820
    Domain: domain:spec (with the packages/cli reader in the same PR, per ruling C)
    Seat: domain:spec#2
    File surface: packages/spec/src/kernel/platform-capabilities.ts + tests (the push token), packages/spec/src/stack.zod.ts (the top-level push key, its COMPOSE_KEY_DISPOSITIONS row) + a stack test, packages/spec/src/system/push-config.zod.ts (docblock and markers only), packages/cli (serve composing PushServicePlugin, the boot refusals, the dependency), the services.push runtime-services docs page, regenerated artifacts, .changeset/29820-*.md. ⛔ Not ADR-0012 (#29817, spec seat 3).
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: xhigh (p1); contract review at CONTRACT_REVIEW_TIER in seat before landing
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: a deployment cannot turn on push from a stack file | no push token, no push key, no os serve reader | os serve and the operator's stack file
    Thread-read: 6601490513
    Premise: holds at main 6a4737ab: PR #29794 merged at 2026-11-03T16:30Z, so @objectstack/service-push and PushServicePlugin are on main; the plugin refuses a relay config at init (the relay sender is #29920). Blocked-by #29794 is cleared.
    Serial constraints cleared: #29817 (ADR-0012 amendment) is claimed by spec seat 3 and edits no file above; the executor re-checks open PRs at PR time.

  9. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 3, 2026
  10. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzgjFwEiezTWdb7bk9i
    os-dev-report
    Stamp: 2026-11-03T18:31Z

    {
      "issue": 29820,
      "status": "done",
      "branch": "claude/project-thread-rx3xpv",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29968",
      "head": "0f588fe6af5c74301cc811687869e8d508503318",
      "session": "session_01WGufzgjFwEiezTWdb7bk9i",
      "premise_still_valid": true,
      "summary": "Ruling C in one PR: the spec `push` capability token (provided by @objectstack/service-push, edition open, requested and not always-on), the top-level stack `push` key (PushServiceConfigSchema, credentialRef only, COMPOSE_KEY_DISPOSITIONS single), and the serve reader: core CAPABILITY_PROVIDERS.push plus resolveCapabilityArgument reading resolvePushCapabilityArg off the provider module, so `objectstack serve` and verify's bootStack share one rule. The reader refuses the relay (absent key, null, {}, transport relay) at boot with PUSH_RELAY_NOT_SERVED_MESSAGE, which names the missing relay sender and prescribes transport: 'direct', and refuses any schema-refused config (inline secrets first) naming paths but never values. A reverse pin asserts every open-edition token has a serve mount, so none is dropped silently. New services.push docs page; push-config.zod.ts docblock updated, the [PLANNED] marker kept only on the relay; cli and verify depend on service-push.",
      "tests": "All under scripts/pm/os-verify-lock.sh. Full build turbo 120/120, VERDICT command-exit 0; spec rebuilt after the last spec src change, exit 0. spec test 808/808 files, 24288 passed + 1 todo; spec typecheck exit 0; spec test:repo 89/89 files, 1524/1524. cli unit tier 377/377 files, 5316/5316; cli typecheck exit 0 (integration tier left to CI: no spawn entry touched). core test + typecheck exit 0; service-push test + typecheck exit 0; verify test 48/48 files, 408/408, typecheck exit 0; metadata-protocol test 311 passed + 4 skipped files, 22320 passed + 26 skipped. ESLint over the 19 changed ts/mjs files exit 0. Ablation via scripts/ablation-replace.mjs of the relay refusal in service-push src/capability-arg.ts (`if (config.transport !== 'direct') {` to `if (false /* ablated-29820 */) {`, anchor 1 to 0, blob 8638e2857a5c to 2c1a8ad32f78): capability-arg.test.ts 5 failed / 7 passed (all five relay rows), cli serve-stack-email-sms-door.contract.test.ts 3 failed / 7 passed (no key, {}, relay). Restored: blob == HEAD 8638e2857a5c, git diff HEAD empty; same files 12/12 and 10/10.",
      "gates": "dispatch-gates --commands on the final tree derives 142 commands; each run, exit codes recorded, all 142 exit 0. --ran: 142 derived accounted for, 142 run, 0 NOT-MEASURED, exit 0. First pass had 4 reds, each fixed in the diff: check:skill-top-level-keys (push added to the platform skill), check:test-source-alias (cli vitest alias for service-push), check:docs-audit-scope (audit scope regenerated with --write), check-plugin-teardown-shape --self-test (exit 3 from the shallow clone; 0 after fetching its base commit). spec check:generated exit 0. check-changeset-no-major --base origin/main with the final PR body: exit 0, LEVEL AXIS ok for yes (widening) + added; control with Metadata-protocol none: exit 1, refused only because the entries are graded minor. CI check-runs not yet read: the PR was just opened (in_progress).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "1 — mcp__github__pull_request_read (get, PR 29968 draft-state read-back); no MCP write tool used",
      "api_writes": "7 — git push to claude/project-thread-rx3xpv x5 (one more attempt was refused locally by the commit-trailer hook before sending and was re-sent after rewording the unpublished tip commit); POST /repos/objectstack-ai/objectstack/dispatches x1 (relay pr_create, draft, no assignees, no reviewers, no labels; read-back identical 11937/11937 bytes, assignees none); this os-dev-report comment via post-stamped.mjs x1",
      "deviations": [
        "File surface wider than the order's wanted list, each forced by the change: core (provider row + argument), service-push (the reader), verify (dependency + bootStack pin), spec unmanaged-install table + artifact-envelope list (classification tests), skills/objectstack-platform/SKILL.md (required gate check:skill-top-level-keys), scripts/docs-audit/handwritten-docs.json (check:docs-audit-scope), packages/cli/vitest.config.ts (check:test-source-alias).",
        "Tier H: the skills/** edit makes this PR governed; it needs a GOVERNED_APPROVERS approval before it can land. Ratchet paid in the same file by deleting a stale path and count: 429 to 429 lines, 6011 to 6004 tokens; bundle SKILL.md lines 4539 to 4539.",
        "PR body order follows the order file (attribution, Fixes, declaration lines, Skills-docs, then the 6-line block), consistent with os-dev.md.",
        "The harness's post-PR assignee/reviewer and PR-subscription steps were skipped per os-dev rule 2 and the dispatch contract.",
        "Human Minimum item 3 left unchecked: ESLint ran over the changed files only; the full pnpm lint is left to CI."
      ],
      "skills_docs": "follow-up owed — skills/objectstack-platform/evals/config-plugins-ops.json: no eval exercises requires push or the push key; the seat files the card and rewrites the line to the card number before ACCEPT.",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: c · reach: named producer skills/objectstack-platform/SKILL.md · its requires table already omitted mailbox and package-registry before this diff · dedupe words: platform skill requires table missing mailbox package-registry",
        "carrier: PR 29968 acceptance notes · noted, not filed",
        "relay sender itself stays with #29920; ADR-0012 untouched (#29817)"
      ]
    }
  11. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Nov 3, 2026
  12. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

    Landed. PR #29968 merged through the queue (merge commit 9ebca947); this card closed as completed by its Fixes line.

    Reading Value
    PR #29968 MERGED at 2026-11-04T10:36Z, head 3612ed4d, contract review PASS 6612508737 + delta 6613694045, ACCEPT 6614280468, authorized APPROVED review by os-zhuang (5766700562) on that head
    Card #29820 closed (completed)

    State change: pm:dispatched removed. What shipped: the push platform capability (@objectstack/service-push, edition open), the top-level push stack key (credentials by credentialRef only), and os serve composing PushServicePlugin; a relay config is refused at boot naming the missing relay sender, and an inline secret is refused at authoring and boot; the services.push runtime-services page. Left elsewhere: the relay sender is #29920; the skill row clause and the eval for push ride #29969.

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

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatenhancementNew 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