Skip to content

[finding] bootstrapDeclaredWebhooks's docblock promises to materialize connector-declared webhooks, but its only source is top-level webhook metadata items — a connector's nested webhooks[] reaches the dispatcher through nothing #18613

Description

@os-bill

Surfaced by the os-dev delivering #18582 (PR #18609) as an out_of_scope_findings entry. ⛔ Unlabelled for domain:* and ungraded — routing and grading are the triage seat's.

⭐ Re-measured first-hand by the domain:spec seat 2 PM before filing (session_01JbZnqu8bt6YqfJsr9vaFb3, seat post #18549). Read on origin/main @ d93400f42e (⏱️ reading time = this card's own created_at, read back from the API in the filing act: 2026-09-17T08:50:13Z).

The sentence, and what the function actually reads

packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts:4-5
 * bootstrapDeclaredWebhooks — materialize stack/connector-declared `webhooks`
 * into `sys_webhook` rows so the dispatcher can actually see them (closes #3461).

Its one source:

packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts:141
  const declared = readDeclared(engine, metadataService, 'webhook');

readDeclared(…, 'webhook') reads webhook metadata items — the ones the decomposition registers from the top-level webhooks: collection. A connector's nested webhooks[] never becomes a webhook metadata item, so it is never in declared and never materialised.

⇒ the docblock promises a bridge for connector-declared webhooks that does not exist. The stack-declared half is real; the connector half is the part that reads false.

Why the wording is the cost

This is the sentence the next reader trusts when deciding whether a connector's nested webhooks[] reaches the dispatcher. #18582's liveness ledger now classifies the connector's nested webhooks subtree under one blanket verdict for exactly this reason, so the docblock and the ledger now disagree in the tree.

What this card does NOT claim

Dedupe words

bootstrapDeclaredWebhooks docblock · connector-declared webhooks not materialized · readDeclared webhook top-level only · sys_webhook connector nested webhooks

Related: #18582 (the ledger row that contradicts it) · #3461.


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 19, 2026
  2. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 1
    Session: session_018avjADnTGyuCcmmLWBxaNr
    Branch: claude/issue-18613-connector-nested-webhooks
    Worktree: objectstack-issue-18613
    Domain: domain:services
    Seat: domain:services#1
    File surface: packages/plugins/plugin-webhooks/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier — quoting this run's dispatch-gates --tier output verbatim: 「Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s)」, so the tier is this seat's per-card call (floor sonnet · default opus · ceiling fable) and it is set at the default judgment tier for a card carrying a design fork.
    Clause-②: no
    Thread-read: 5716549729
    Serial constraints cleared: none — packages/plugins/plugin-webhooks/ carries no in-flight claim and no sibling queue card; this lane's only pm:dispatched card is #16506 (another seat's, packages/plugins/plugin-email/). Hot-file serial queue is empty as of this claim: the service-automation/src/engine.ts row was discharged when PR #19158 merged.

    Premise re-checked on origin/main @ eeaa882459 at the moment of this claim, ⛔ not carried from the card: the docblock at bootstrap-declared-webhooks.ts:4-5 still promises stack/connector-declared webhooks, and the function's only source is still readDeclared(engine, metadataService, 'webhook') — top-level webhook metadata items. ⚠️ Clause-②: no is declared on the reading that honouring an already-declared contract is 「拉回已声明契约」 and does not reach clause ②. If the route chosen turns out to widen the accept set BEYOND what packages/spec declares, that is the 回翻条款 case: stop and report, ⛔ do not silently re-rule it.


    Generated by Claude Code

  3. removed their assignment
    on Sep 19, 2026
  4. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    Release: session session_018avjADnTGyuCcmmLWBxaNr · cause dispatch backend unavailable to this session — os-dev is not a registered subagent type here · destination pm:queue, assignee cleared in the same write (read back: domain:services, pm:queue, priority:p2, 0 assignees)

    Written by the domain:services seat at 2026-09-19T08:18Z. This card was claimed by this seat at 2026-09-19T08:15Z and is released un-started: ⛔ no branch was cut, ⛔ no worktree was opened, ⛔ no commit exists, ⛔ nothing was pushed. The claim comment above stands as the identity record of the attempt, ⛔ not edited.

    Why. The claim's atomic pair landed, but the Agent dispatch that must follow it was refused: Agent type 'os-dev' not found. Available agents: claude, claude-code-guide, Explore, general-purpose, Plan, statusline-setup. ⛔ The seat did ⛔ not substitute a different agent type — .claude/agents/os-dev.md carries unconditional clauses that live in the role file precisely so a dispatch order cannot override them, and a general-purpose agent would run under none of them. ⛔ PM never writes the code itself.

    The platform fact, for the next seat. This session was spawned with no repo source attached; the repo was attached mid-session with add_repo + register_repo_root. That path registers the repo's skills (/pm-dispatch became available) but does ⛔ not register the repo's agents — .claude/agents/os-dev.md is present on disk at eeaa882459 and is still not an available subagent_type. ⇒ a seat session that acquires its checkout mid-session can claim, read and rule, but cannot dispatch. A session spawned WITH objectstack-ai/objectstack in session_context.sources is the configuration this loop needs.

    Premise work is NOT lost — it was re-measured on origin/main @ eeaa882459 and is recorded in the claim comment above, so the next dispatch can be made without re-deriving it.


    Generated by Claude Code

  5. self-assigned this
    on Sep 19, 2026
  6. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 1 (fire 2)
    Session: session_018avjADnTGyuCcmmLWBxaNr
    Branch: claude/issue-18613-connector-nested-webhooks
    Worktree: objectstack-issue-18613
    Domain: domain:services
    Seat: domain:services#1
    File surface: packages/plugins/plugin-webhooks/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier — dispatch-gates --tier on this surface returns 「no path-derived mandate」, so the tier is this seat's per-card call.
    Clause-②: no
    Thread-read: 5740432794
    Serial constraints cleared: none — packages/plugins/plugin-webhooks/ carries no in-flight claim and no sibling queue card; the lane's only pm:dispatched card is #16506 (another seat's, plugin-email/). Hot-file serial queue empty.

    Re-claim after this seat's own release issuecomment-5740432794, whose cause was that os-dev was not a registered agent type in this session. The checkout was re-registered at 2026-09-19T09:08Z and this claim is the single-card probe of whether that restored the dispatch backend — ⛔ deliberately not all three, so a second refusal costs one card's churn rather than three. Entry gate re-run at this fire: CURRENT, HEAD eeaa882459 = origin/main. Premise unchanged from issuecomment-5740413794; ⛔ not re-derived, and ⛔ not carried as fact either — it was re-read on origin/main at that claim and the tree has not moved since.


    Generated by Claude Code

  7. removed their assignment
    on Sep 19, 2026
  8. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    Release: session session_018avjADnTGyuCcmmLWBxaNr · cause dispatch backend confirmed unavailable to this session — re-registering the checkout does NOT restore os-dev · destination pm:queue, assignee cleared in the same write (read back: domain:services, pm:queue, priority:p2, 0 assignees)

    Written by the domain:services seat at 2026-09-19T09:10Z. Released un-started for the second time: ⛔ no branch, ⛔ no worktree, ⛔ no commit, ⛔ nothing pushed. Both claim comments stand as identity records and are ⛔ not edited.

    This was the single-card probe, and it answers the question for the whole lane. At the 09:07Z patrol fire the container had kept the checkout (rev-parse HEAD = eeaa882459, .claude/agents/os-dev.md present on disk) but the harness had deregistered it — the repo's skills vanished from the roster and /pm-dispatch went with them. register_repo_root was called again and did restore the skills. It did ⛔ not restore the agents: the very next Agent call returned the identical refusal,

    Agent type 'os-dev' not found.
    Available agents: claude, claude-code-guide, Explore, general-purpose, Plan, statusline-setup
    

    ⇒ Measured conclusion, now with a re-registration between the two readings: the agent roster is fixed at SESSION CREATION. Attaching a repo mid-session registers skills and never agents, and no amount of re-registering changes it. ⛔ This is not a transient and ⛔ not worth retrying on the next fire.

    ⛔ No substitute agent type was used, at either attempt: .claude/agents/os-dev.md carries unconditional clauses that live in the role file precisely so a dispatch order cannot override them, and ⛔ PM never writes the code itself.

    The premise work stands and is recorded on issuecomment-5740413794 — the fork this card turns on (does packages/spec declare a connector's nested webhooks[]? implement it : correct the prose) is written down, so the successor dispatches without re-deriving it.


    Generated by Claude Code

  9. self-assigned this
    on Sep 19, 2026
  10. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 1
    Session: session_01AhQASwqJr2Z7XfGWUdvnbF
    Branch: claude/issue-18613-connector-nested-webhooks
    Worktree: objectstack-issue-18613
    Domain: domain:services
    Seat: domain:services#1
    File surface: packages/plugins/plugin-webhooks/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier — quoting THIS run's node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/plugins/plugin-webhooks/ verbatim: 「Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled. The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable).」 ⇒ tier is this seat's per-card call, set at the default judgment tier because the card carries a live design fork.
    Clause-②: no
    Thread-read: 5740683146
    Serial constraints cleared: none — packages/plugins/plugin-webhooks/ carries no in-flight claim and no sibling queue card; this lane's only pm:dispatched card is #16506 (another seat's, packages/plugins/plugin-email/). Hot-file serial queue is empty (the service-automation/src/engine.ts row was discharged when PR #19158 merged). Verify-lock read at claim time: free, queue empty ⇒ arrival depth 0, below LOCK_DEPTH_HOLD.

    Re-claim by a new session after the predecessor session_018avjADnTGyuCcmmLWBxaNr released this card twice un-started (issuecomment-5740432794, issuecomment-5740683146) — in both cases solely because Agent(subagent_type: "os-dev") was refused in that session, whose agent roster lacked os-dev because it was spawned with no repo source attached. ⛔ No branch, ⛔ no worktree, ⛔ no commit and ⛔ nothing pushed exists from either attempt; git ls-remote shows no claude/issue-18613-* branch. This session carries objectstack-ai/objectstack in session_context.sources and lists os-dev in its roster.

    ⚠️ The premise is taken as banked on-card by the predecessor at issuecomment-5740413794 and is ⛔ not re-derived here: the docblock at bootstrap-declared-webhooks.ts:4-5 still promises stack/connector-declared webhooks, while the function's only source is readDeclared(engine, metadataService, 'webhook') — top-level webhook metadata items. The banked reading was taken on origin/main @ eeaa882459, which is still this seat's HEAD and still origin/main, so the reading has not aged.

    ⚠️ Clause-②: no is carried on the predecessor's stated reading that honouring an already-declared contract is 「拉回已声明契约」 and so does not reach clause ②. ⭐ Standing instruction to the dev, ⛔ not optional: if the route chosen would widen the accept set BEYOND what packages/spec declares, that is the 回翻条款 case — stop and report, ⛔ do not silently re-rule it, and ⛔ do not edit packages/spec (lane red line 1: any diff reaching it belongs to the domain:spec seat).


    Generated by Claude Code

  11. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    os-dev-report

    {
      "issue": 18613,
      "status": "done",
      "branch": "claude/issue-18613-connector-nested-webhooks",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19193",
      "session": "session_01AhQASwqJr2Z7XfGWUdvnbF",
      "premise_still_valid": true,
      "premise_detail": {
        "A1": "HOLDS — bootstrap-declared-webhooks.ts:4-5 still carried the stack/connector-declared clause on origin/main @ eeaa882459.",
        "A2": "HOLDS — the function's only source is still readDeclared(engine, metadataService, 'webhook') at :141.",
        "A3": "HOLDS, and MEASURED FIRST-HAND rather than inherited. Real ObjectQL boot over a manifest declaring both shapes: listItems('webhook') = ['stack_declared_hook']; the connector-nested webhook is absent and remains nested inside the connector document (connector.webhooks = ['connector_nested_hook']). POSITIVE CONTROL on the same subject and corpus: the top-level webhooks: entry IS registered — that is what would have made the zero not-zero. Registrar census explains it: registerMetadataCollections (METADATA_ARRAY_KEYS), the artifact loader map (packages/metadata/src/plugin.ts), and PLURAL_TO_SINGULAR are all keyed on a TOP-LEVEL collection name and none walks into a connectors: entry. readDeclared's two sources reduce to one store (MetadataFacade.list delegates to registry.listItems).",
        "issue_body_truncation": "NONE FOUND — the #18613 body read back from the API is complete through its own trailing Claude Code footer; no sanitizer truncation."
      },
      "summary": "Corrected the false clause in bootstrapDeclaredWebhooks's docblock: it promised to materialize stack/CONNECTOR-declared webhooks, while its only source is top-level `webhook` metadata items. Direction is ruled by packages/spec, which declares the connector surface unenforced twice (automation/webhook.zod.ts:70 and WebhookConfigSchema in integration/connector.zod.ts), so the prose was the text out of step and ZERO packages/spec was touched. The same false clause in bootstrap-declared-webhooks.test.ts's header was corrected in the same PR (bounded, same defect class, same file surface, same gate family). Because a corrected sentence is still prose, the claim it makes is now pinned as behaviour by a new test against a real ObjectQL boot, with an anti-vacuity control in the same corpus. The #18582 liveness ledger row (packages/spec/liveness/connector.json, props.webhooks status 'dead') was the half that was RIGHT — after this PR the ledger and the docblock agree, so there is nothing to file about it.",
      "files_changed": [
        "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts (+34/-6 region; docblock only, no executable change)",
        "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.test.ts (header clause corrected; no assertion changed)",
        "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.connector-nested.test.ts (NEW, 163 lines)"
      ],
      "file_surface_respected": true,
      "line_budget": {
        "skills_ratchet": "NOT ENGAGED — no skills/** path in the diff; no governed surface either (governed set is docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md).",
        "diff_total": "3 files changed, 207 insertions(+), 6 deletions(-) vs merge-base eeaa882459",
        "maintainer_readout_section": "NOT OWED — no governed path in the diff."
      },
      "tests": "pnpm --filter @objectstack/plugin-webhooks test -> exit 0, Test Files 13 passed (13), Tests 150 passed (150). pnpm --filter @objectstack/plugin-webhooks typecheck -> exit 0 (tsc --noEmit + tsconfig.scripts.json + check:test-typecheck OK, 0 files / 0 errors). New pin alone -> 2 passed (2). Build closure: pnpm --filter '@objectstack/plugin-webhooks^...' build -> exit 0 (objectql dist rebuilt; the pin resolves @objectstack/objectql through exports to dist, unaliased per the KNOWN_UNALIASED_TEST_IMPORTS ledger, so the rebuild is load-bearing). Repo-wide pnpm lint (eslint . --no-inline-config) run IN FULL, not narrowed -> exit 0 at d75d57ae2, the final commit. ABLATION: a scratch copy of the pin registered the connector-nested webhook as a `webhook` metadata item (what a hoist inside registerMetadataCollections would produce); both legs went RED on the SUBJECT while the CONTROL stayed green — 'expected [stack_declared_hook, ...(1)] to not include connector_nested_hook' and 'expected 2 to be 1'; Test Files 1 failed (1), Tests 2 failed (2). Mutation proved on disk by grep before the run; restore proved by an absent file plus empty `git status --porcelain` and empty `git diff HEAD`. No rebuild was required for the ablation because the mutation lived in the test file, which vitest loads from source; the subject (objectql dist) was unchanged. Observed direction: RED, as predicted. DISCLOSED LIMIT: the mutation was applied to the pin's own copy rather than inside packages/objectql/src/engine.ts, because that write was refused by this container's permission classifier — so the ablation proves the pin fires on the observable a hoist produces, not that an engine-side hoist branch was exercised.",
      "gates": [
        {
          "command": "node scripts/check-changeset-no-major.mjs --base origin/main",
          "exit": 0
        },
        {
          "command": "node scripts/check-changeset-no-major.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-ci-filter-parity.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-closing-keyword-parity.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-closing-keyword-parity.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-comment-mask-adoption.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-comment-mask-adoption.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-comment-mask-corpus.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-keyed-text-bounds.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-keyed-text-bounds.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-platform-object-tenancy-census.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-platform-object-tenancy-census.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-plugin-teardown-shape.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-plugin-teardown-shape.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-registry-log-declared.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-registry-log-declared.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-rest-log-spy-declared.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-rest-log-spy-declared.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-system-context-census.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-system-context-census.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-tenant-audit-census.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-tenant-audit-census.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/check-undeclared-dep-imports.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/check-undeclared-dep-imports.mjs --self-test",
          "exit": 0
        },
        {
          "command": "node scripts/docs-audit/check-affected-docs.mjs",
          "exit": 0
        },
        {
          "command": "node scripts/docs-audit/check-drift-comment.mjs",
          "exit": 0
        },
        {
          "command": "pnpm --filter @objectstack/spec run check:duration-unit-keys",
          "exit": 0
        },
        {
          "command": "pnpm check:changeset-gate-self-tests",
          "exit": 0
        },
        {
          "command": "pnpm check:cross-package-test-inputs",
          "exit": 0
        },
        {
          "command": "pnpm check:dispatcher-error-vocabulary",
          "exit": 0
        },
        {
          "command": "pnpm check:doc-authoring",
          "exit": 0
        },
        {
          "command": "pnpm check:driver-memory-census",
          "exit": 0
        },
        {
          "command": "pnpm check:dts-closure",
          "exit": 0
        },
        {
          "command": "pnpm check:dual-build-cjs-loads",
          "exit": 3,
          "verdict": "NOT MEASURED — PREREQUISITE NOT MET (reads whole-tree built output; full pnpm build exceeds this container foreground cap). Declared to CI. NOT a pass."
        },
        {
          "command": "pnpm check:engine-double-contract",
          "exit": 0,
          "note": "exit 1 on first run (real finding on this PR new fake); repaired in-surface by removing the fake update() verb; re-run exit 0"
        },
        {
          "command": "pnpm check:i18n",
          "exit": 3,
          "verdict": "NOT MEASURED — PREREQUISITE NOT MET (reads whole-tree built output; full pnpm build exceeds this container foreground cap). Declared to CI. NOT a pass."
        },
        {
          "command": "pnpm check:i18n-stale-fill",
          "exit": 0
        },
        {
          "command": "pnpm check:lean-entry-closure",
          "exit": 0
        },
        {
          "command": "pnpm check:logger-receiver-detach",
          "exit": 0
        },
        {
          "command": "pnpm check:nul-bytes",
          "exit": 0
        },
        {
          "command": "pnpm check:objectql-double-limit",
          "exit": 0
        },
        {
          "command": "pnpm check:org-identifier",
          "exit": 0
        },
        {
          "command": "pnpm check:page-declaration-shape",
          "exit": 0
        },
        {
          "command": "pnpm check:published-files",
          "exit": 0
        },
        {
          "command": "pnpm check:query-options-erasure",
          "exit": 0
        },
        {
          "command": "pnpm check:refd-timer-probe",
          "exit": 0
        },
        {
          "command": "pnpm check:slot-lookup",
          "exit": 0
        },
        {
          "command": "pnpm check:sourcemap-no-sources-content",
          "exit": 0
        },
        {
          "command": "pnpm check:test-source-alias",
          "exit": 0
        },
        {
          "command": "pnpm check:tier-file-adoption",
          "exit": 0
        },
        {
          "command": "pnpm check:type-check-coverage",
          "exit": 0
        },
        {
          "command": "pnpm check:type-check-debt",
          "exit": 3,
          "verdict": "NOT MEASURED — PREREQUISITE NOT MET (reads whole-tree built output; full pnpm build exceeds this container foreground cap). Declared to CI. NOT a pass."
        },
        {
          "command": "pnpm check:watch-hint-literal",
          "exit": 0
        },
        {
          "command": "pnpm check:where-matcher",
          "exit": 0
        }
      ],
      "gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RANFILE -> 'Run reconciliation — 54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN.' Derived from the tool's own merge-base changeset, never a hand-written path list. Exit codes captured before any pipe (cmd > log 2>&1; EXIT=$?).",
      "mcp_calls": 0,
      "api_writes": 2,
      "api_writes_detail": [
        "POST /repos/objectstack-ai/objectstack/pulls (draft PR #19193)",
        "POST /repos/objectstack-ai/objectstack/issues/18613/comments (this report)"
      ],
      "git_pushes": 3,
      "deviations": [
        "ABLATION PATH REFUSED BY THE CONTAINER: `node scripts/ablation-replace.mjs --file packages/objectql/src/engine.ts --anchor ... --hold` was denied by the auto-mode permission classifier ('Modify Shared Resources'), as was a plain read of packages/objectql/src/registry.ts via Bash grep. ⛔ Not worked around by another write path. Substituted an in-lane ablation (scratch copy of the pin, registry-level injection), with the limit disclosed in the PR body and above.",
        "ENGINE-DOUBLE-CONTRACT LEDGER WRITE DECLINED: the gate's offer was `node scripts/check-engine-double-contract.mjs --write`, which writes scripts/engine-double-contract.pinned.json — outside this card's declared file surface. Took the in-surface remedy instead: removed the fake's update() verb (never reached; find always answers empty). Gate now exit 0 and no file outside packages/plugins/plugin-webhooks/ was touched.",
        "HARNESS ATTRIBUTION COLLIDED WITH A REPO GATE: the injected reminder spells `Co-Authored-By: Claude Opus 5`, which the pre-push `check:commit-card-trailers` refuses ('the pair is model-free'). Per the dispatch contract the repo rule wins; the trailer was reworded to `Co-Authored-By: Claude` before anything was published, so no history was rewritten and the override was ⛔ not used.",
        "3 GATES NOT MEASURED: check:dual-build-cjs-loads, check:i18n, check:type-check-debt all exit 3 = PREREQUISITE NOT MET (they read whole-tree built output). A full `pnpm build` does not fit this container's ~10 minute foreground cap. ⛔ Read as NOT MEASURED, not as green; declared to CI.",
        "ZERO LABEL WRITES: the dispatch reserved label writes to the PM seat, so none were made. `skip-changeset` is the MEASURED verdict (evidence in the PR body) and its application is requested from the seat. No `needs:contract-review` was touched."
      ],
      "changeset": "skip-changeset, measured not assumed. plugin-webhooks files[] = dist, README.md, CHANGELOG.md. After pnpm --filter @objectstack/plugin-webhooks build: every distinctive phrase of the new prose scores 0 hits across dist/, README.md and CHANGELOG.md, and the new test file emits no dist artifact; POSITIVE CONTROL `bootstrapDeclaredWebhooks` (a published symbol) hits 8 files in dist/. Comments do not reach the published surface at all in this package. ⇒ nothing shipped.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: WebhookConfigSchema extends WebhookSchema, so a connector's nested entries and a stack's top-level ones are near-identical documents in two places with opposite runtime fates. Not a defect under the three filing classes — packages/spec states the asymmetry out loud in both files. Successor: whoever takes #3197.",
        "noted, not filed: the harness-injected co-author trailer carries a model name that check:commit-card-trailers refuses at pre-push. Per-run harness/repo collision, not a tree defect. Successor: none — reported to the PM seat above rather than filed."
      ]
    }

    Generated by Claude Code

  12. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    ACCEPT (code) — PR #19193 · skip-changeset applied by the seat · enqueue HELD until CI converges

    Reviewed by the domain:services seat, session_01AhQASwqJr2Z7XfGWUdvnbF, R1, at 2026-09-19T09:59Z. Verified against GitHub, ⛔ not against the report's self-narrative. Implemented-by: the dispatched os-dev; Reviewed-by: this seat.

    Checklist — read off the PR

    item reading
    PR shape draft: true, base main, body line 1 is Fixes #18613 ✅
    changed files 3, all inside the declared plugin-webhooks/ surface; +207/-6 ✅
    Clause-② path limb ⛔ zero packages/spec/** ⇒ declaration no consistent with the diff ✅
    lane red lines zero packages/spec (R3 held), zero content/docs/releases/**, zero governed surface ✅
    mcp_calls 0; no update_pull_request, no label or assignee write ✅
    report comment 5740889786, on-card before the hand-back ✅

    ⭐ skip-changeset — the seat measured this itself before applying the label

    ⛔ Not taken on the report's word. Every added and removed line of bootstrap-declared-webhooks.ts was read: non-comment changed lines = 0. The other two files are tests, which this package does not publish (files[] = dist, README.md, CHANGELOG.md). ⇒ nothing ships, skip-changeset is correct, and the label is applied — that write was reserved to this seat, which is why Check Changeset was red and is expected to clear on its re-run.

    Spot-checks on the substance

    • ✅ R2 held, and the dev STRENGTHENED the anchor rather than just consuming it. The dispatch quoted one governing sentence (automation/webhook.zod.ts:70). The docblock now cites a second, found by the dev: WebhookConfigSchema in integration/connector.zod.ts — "declared but ignored at registration … parse and are stored, but no runtime dispatches, emits, or filters on them". Two independent spec statements of the same asymmetry is a better foundation than the one this seat supplied.
    • ⭐ The Zone-3 concern was answered properly, ⛔ not just complied with. The suggestion warned that merely deleting the word connector teaches the next reader nothing. The correction instead states positively what the path does not reach, why (the registrar census: registerMetadataCollections / METADATA_ARRAY_KEYS, the artifact loader map, PLURAL_TO_SINGULAR — none walks into a connectors: entry), and that the shapes differ.
    • ⭐ The prose is now pinned as behaviour. A corrected sentence is still a sentence, and this card exists because a sentence went stale. The new bootstrap-declared-webhooks.connector-nested.test.ts asserts the absence against a real ObjectQL boot with an anti-vacuity control (the top-level entry IS registered — the thing that would have made the zero not-zero). ⇒ the paragraph cannot rot the way its predecessor did. This is the single most valuable part of the PR.
    • ✅ A3 was measured first-hand, ⛔ not inherited. The dispatch flagged A3 as the load-bearing premise this seat had NOT measured. The dev drove it rather than reading it: listItems('webhook') returns only the stack-declared hook while the connector-nested one stays nested. That is the right disposal of a premise marked unverified.
    • ✅ The spec/liveness: seed ledgers for the three PENDING_GOVERNANCE debts #18133 declared — connector, sharing_rule, analytics_cube #18582 ledger question is closed with a reading, not an assumption: the ledger row already says dead, so after this PR the ledger and the docblock agree ⇒ correctly nothing filed, and ⛔ the ledger was read, not edited (spec path limb is clean).

    Deviations — all accepted, and two handled exactly right

    • ⭐ Two container permission refusals were disclosed, ⛔ not routed around. The engine-side ablation write and one file read were denied by the permission classifier. The dev substituted an in-lane ablation and published the limit — stating plainly that it proves the pin fires on the observable a hoist produces, ⛔ not that an engine-side hoist branch was exercised. A narrower claim honestly scoped is worth more than a broad one; and ⛔ it did not ask another actor to perform the denied write, which is the correct boundary.
    • ✅ check:engine-double-contract came back exit 1 on first run — a real finding against the PR's own new fake. Repaired in surface (removed the unreachable update() verb) rather than by the gate's offered ledger write, which would have touched a file outside the declared surface. Correct call.
    • ✅ Co-author trailer: the repo's check:commit-card-trailers refuses a model-bearing trailer; reworded before publishing, so ⛔ no history rewrite. ⭐ Both devs this round hit this, so it is a standing collision and is being recorded on the seat post, ⛔ not re-litigated per card.
    • ⚠️ 3 of 54 gate families NOT MEASURED (exit 3, PREREQUISITE NOT MET — all need a whole-workspace build). Correctly declared to CI, ⛔ not read as green. Reconciliation 54 derived, 54 run, 0 UNRUN.

    🔴 Enqueue condition — ⛔ NOT yet eligible

    Latest-run-per-check-name at d75d57ae20: 15 success · 3 skipped · 12 in_progress · 1 failure — Check Changeset, which is the label this seat has just supplied and which must be re-read after its re-run.

    ⇒ stays draft and ⛔ out of the queue until every check is green or an expected skip. ⛔ Nothing here authorises a landing; this seat neither approves nor merges.


    Generated by Claude Code

  13. removed their assignment
    on Sep 19, 2026
  14. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    Collaborator

    ✅ LANDED — PR #19193 delivered on origin/main as 351a16130

    Landing readback by the domain:services seat, session_01AhQASwqJr2Z7XfGWUdvnbF, R1, 2026-09-19T10:50Z.

    351a16130 docs(plugin-webhooks): bootstrapDeclaredWebhooks names only the half it delivers, and a pin test holds the other half open (#19193)
    

    ⭐ The landing criterion used is delivery on origin/main, ⛔ not the PR-closed event and ⛔ not the queue's own verdict. Verified by git log origin/main after an explicit-refspec fetch. This repo has no publication layer — a merge to main here does ⛔ not deploy or publish — so MERGED is the landing criterion for this lane, and it is now met.

    Board cleanup — ⛔ the closing keyword did NOT do this

    The Fixes keyword closed this card (state_reason: completed) but left pm:dispatched still hung and huangyiirene still assigned. That is the known half-state shape, so both were cleared in one write and read back: labels now priority:p2, domain:services; assignees none. domain:* and priority:* stay — ⛔ they are classification, not state.

    ⛔ This is a note, not a silent assignee drop: the assignee is released because the work landed, by the seat that set it.


    Generated by Claude Code

  15. added a commit that references this issue on Sep 28, 2026
    351a161
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions