Skip to content

pm/fleet-write: a relay op that creates a DRAFT repository security advisory (POST /security-advisories) under the stroke-scoped advisories token, so security findings are not capped by the one-report-a-day private-report limit #29410

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB

Filing gate: ③ a maintainer-directed task. Maintainer word, verbatim: 「立一张 skills 卡,我现在就派发,然后你再试」 (Warren, project thread "Plan the release checklist run", 2026-11-02 14:17 UTC), answering the proposal below; it is also his naming of this pm-rule change (filing-gate: a card that adds or changes a pm rule is filed only when the maintainer names it).
Business reason: security findings now go to private advisories through the relay (#29178, #29222). GitHub caps NEW private vulnerability reports per account per day (https://github.blog/changelog/2026-10-01-rate-limits-for-private-vulnerability-reports), and every relay report is filed as the one fleet App account, so the whole fleet gets about one report a day; on 2026-11-02 the first report landed and the next eight, from two sessions, were refused 403 (#29402). The App cannot be put on the trusted-reporter allow list. Without this op, findings back up for days or fall back to hand delivery.
Reader who acts: the domain:skills seat (owner of scripts/pm/** relay tooling and .claude/skills/pm-dispatch/**), dispatched by the maintainer now.
Duplicate search: is:issue advisory_create → 0 hits; is:issue "draft advisory" relay → 18 hits, the nearest are #29160 and #29111 (both closed: they built the report/collaborator/state rows and the security rule), none adds draft creation.
Related: #29402 (write-pace reads the 403 as an egress-proxy refusal; the relay hides GitHub's message).

Platform reading this rests on

POST /repos/{owner}/{repo}/security-advisories ("Create a repository security advisory") creates an advisory in draft. Per the REST docs (https://docs.github.com/en/rest/security-advisories/repository-advisories) it accepts GitHub App installation access tokens with the "Repository security advisories" repository permission (write); the rate limit in the changelog applies to new private reports only. ⚠️ Both are documentation readings: the first live run decides, as #29222's Acceptance notes did for the report row.

Scope

  1. Relay row advisory_create in scripts/pm/fleet-write/ops.mjs (+ validate.mjs, execute.mjs), shaped like advisory_report: alone; mints repository-advisories: write for its stroke only (STROKE_SCOPED_PERMISSIONS); payload read from the event file; closed keys summary, description, severity, cwe_ids, and the minimum vulnerabilities entry the endpoint requires (ecosystem npm, package name, ⛔ no version ranges unless the endpoint refuses without them — measure); ⛔ no state other than the endpoint's default draft, ⛔ published unreachable, ⛔ no credits, CVE request or private fork. Lands on a 2xx carrying a ghsa_id; prints only the id and state, never summary, description or vulnerability text. REFUSED_PATH_FAMILIES opens exactly this one POST path (no GET list, no PATCH of text).
  2. Follow-up stroke unchanged: advisory_collaborators_add then names the finding seat's account on the draft (its fence already admits draft).
  3. Skill text (governed, .claude/skills/pm-dispatch/references/security-findings.md §3 and references/rest-channel.md): replace "建草稿公告须管理员或安全经理,中继无此操作 ⇒ 草稿由维护者建" with: the seat files a finding as a DRAFT advisory through advisory_create (preferred), advisory_report stays for repos where the App lacks the permission; record the per-account daily limit on reports. Keep "⛔ 席位永不以自身账号私报或写公告" (writes stay App-only), and "发布归维护者".
  4. Self-tests in validate.mjs for the new row (closed keys, published unreachable, no value echoed in refusals), and the step-summary / log no-leak marker test extended to it.

Acceptance

  • One live advisory_create from a cloud seat lands a draft advisory on objectstack-ai/objectstack, its id in the run annotation, nothing of its text in the public run log; a second and third in the same hour also land (the measurement that it is not under the report limit).
  • advisory_collaborators_add on that draft lands.
  • pnpm check:pm-skill-ratchet and the fleet-write self-tests green; Tier H path (governed .claude/**), contract review before any approver is asked.

Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB

    Claim: maintainer-directed dispatch · card thread · 2026-11-02T14:20Z
    Session: session_01JQwxbWBtYWqaV8NhvUrShB (the dispatching seat); the card thread's session is named in its first report
    Account: os-warren (the card's assignee)
    Branch: the card thread's harness-pinned claude/project-thread-* branch (it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:skills
    Seat: domain:devx#1 dispatching cross-lane on the maintainer's word (below); no skills seat is asked to yield
    Ruling-ref: Warren, project thread "Plan the release checklist run": 「立一张 skills 卡,我现在就派发,然后你再试」 then 「立一张 skills 卡,你自己派发,开发完成之后,然后你再试」
    File surface: scripts/pm/fleet-write/ops.mjs, scripts/pm/fleet-write/validate.mjs, scripts/pm/fleet-write/execute.mjs (only if the row needs executor code), .claude/skills/pm-dispatch/references/security-findings.md, .claude/skills/pm-dispatch/references/rest-channel.md; ⛔ no .github/workflows/** unless the mint step cannot carry the row as is (stop and report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier at 838a802bf3: no path-derived mandate), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds (read at origin/main 838a802bf3: REFUSED_PATH_FAMILIES admits only one advisory's path and the reports path; security-findings.md §3 says the relay has no draft-create op)
    Serial constraints cleared: #29402 (open, unclaimed) names write-pace.mjs and the advisory refusal text in execute.mjs; this card does not touch write-pace.mjs, and if it edits execute.mjs it stays out of the refusal-message block #29402 owns.

    State: → pm:dispatched, assigned to os-warren, recorded by this claim. Order lives in the project.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbZ5rVEB642GP9VD5Xj
    os-dev-report

    {
      "issue": 29410,
      "status": "done",
      "branch": "claude/project-thread-hir0t0",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29436",
      "session": "session_01JQwxbZ5rVEB642GP9VD5Xj",
      "premise_still_valid": true,
      "summary": "New relay row advisory_create: ONE POST to the bare security-advisories collection (creates a draft), alone in its stroke under the stroke-scoped repository-advisories grant; closed keys summary, description, packages (npm names, one vulnerabilities entry each, ecosystem npm and name only), optional severity and cwe_ids; no state, credits, CVSS vector, CVE id or private fork. Lands on a 2xx carrying a ghsa_id that reads state draft; the row and an advisory annotation name the id alone, and the seat hands it on for advisory_collaborators_add. REFUSED_PATH_FAMILIES opens exactly the bare collection path, and the validator pins that this row's POST is the only request on it. security-findings.md section 3 now prefers advisory_create, keeps advisory_report for repos where the App lacks the grant, records the per-account daily report cap, keeps App-only writes and maintainer-only publishing; rest-channel.md points there. Both governed files net zero lines.",
      "tests": "validate self-test 275 cases / 15 batteries (advisory battery 51, pin 51; pin 52 reds with 'registered 51'); execute self-test 237 / 21 (advisory battery 27, pin 27); dispatch self-test 298 / 26 (seat advisory battery 14, pin 14); ops self-test exit 0. One-time ablation at f68850fd: the draft's landed row also printing the answer's summary reds 2 of 237 execute cases (the row case and THE MARKER TEST); restored with blob equal to HEAD, exit 0.",
      "gates": "local subset at f68850fd: dispatch-gates --commands derived 46, all run, all exit 0 (node scripts/check-closing-keyword-parity.mjs; node scripts/check-closing-keyword-parity.mjs --self-test; node scripts/check-comment-mask-corpus.mjs; node scripts/check-declaration-mirrors.mjs; node scripts/check-declaration-mirrors.mjs --self-test; node scripts/check-self-test-wired.mjs; node scripts/check-self-test-wired.mjs --self-test; node scripts/check-self-test-workflow-commands.mjs; node scripts/check-self-test-workflow-commands.mjs --self-test; node scripts/check-whole-set-label-write.mjs; node scripts/check-whole-set-label-write.mjs --self-test; node scripts/ci/plan-test-core-shards.mjs --self-test; node scripts/pm/bare-root-worklist.mjs --self-test; node scripts/pm/check-harness-current.mjs --self-test; pnpm --filter @objectstack/lint run check:doc-formula-expressions; pnpm check:agent-test-spelling; pnpm check:bash32-floor; pnpm check:ci-filter-parity; pnpm check:cli-command-ids; pnpm check:cross-package-test-inputs; pnpm check:doc-authoring; pnpm check:driver-memory-census; pnpm check:entry-guard; pnpm check:gitlink-declared; pnpm check:nul-bytes; pnpm check:parse-guard; pnpm check:pm-close-cards; pnpm check:pm-dispatch-gates; pnpm check:pm-fleet-write-dispatch; pnpm check:pm-fleet-write-execute; pnpm check:pm-fleet-write-validate; pnpm check:pm-governed-merges; pnpm check:pm-issue-create; pnpm check:pm-issue-transfer; pnpm check:pm-label-write; pnpm check:pm-post-stamped; pnpm check:pm-record-recognisers; pnpm check:pm-skill-id-lint; pnpm check:pm-skill-ratchet; pnpm check:pm-write-pace; pnpm check:pnpm-filter-targets; pnpm check:pnpm-flag-spellings; pnpm check:refd-timer-probe; pnpm check:scripts-symbol-anchors; pnpm check:skill-frame-sync; pnpm check:watch-hint-literal); --ran reconciliation: 46 derived, 46 run, 0 NOT-MEASURED. CI families: the PR's check-runs, in_progress at report time.",
      "line_budget": {
        "security-findings.md": "55 to 55 (ceiling 55)",
        "rest-channel.md": "81 to 81 (ceiling 81)",
        "ratchet": "check:pm-skill-ratchet exit 0, no ceiling row moved",
        "max_changed_line_bytes": 120
      },
      "files_changed": [
        "scripts/pm/fleet-write/ops.mjs",
        "scripts/pm/fleet-write/validate.mjs",
        "scripts/pm/fleet-write/execute.mjs",
        "scripts/pm/fleet-write/dispatch.mjs",
        ".claude/skills/pm-dispatch/references/security-findings.md",
        ".claude/skills/pm-dispatch/references/rest-channel.md"
      ],
      "deviations": [
        "scripts/pm/fleet-write/dispatch.mjs edited outside the claim's file surface: ADVISORY_ANNOTATED_OPS must name advisory_create or the draft's id never reaches the seat.",
        "rest-channel.md advisory line loses its check mark: the line now names an op no seat has called; the seat's live run restores it.",
        ".github/workflows/fleet-write.yml comment above permission-repository-advisories still says THREE consumers; left untouched per the claim (mint step carries the row as is), noted in Acceptance notes.",
        "PR template headings folded into the os-dev section order (What changes / Why / Tests / Acceptance notes / Verification / Deviations / Human Minimum)."
      ],
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, labels skip-changeset), this report comment via post-stamped; plus git push (not REST)",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: none · the workflow comment drift above is a comment, noted in the PR's Acceptance notes · carrier: the next PR touching fleet-write.yml"
      ],
      "acceptance_after_merge": "seat runs: one advisory_create landing a draft (id in the annotation, no text in the run log), a second and third in the same hour, then advisory_collaborators_add on that draft; a refusal stops and goes to the maintainer as status plus listed message"
    }
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p1 (priority added) · 2026-11-02T15:05Z

    ⛔ Not a claim.

    • Read fresh. A maintainer-directed relay op that creates draft security advisories, already claimed. Part of the security channel, so p1.

    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB

    ACCEPT → PR #29436 comment 6580686659 · 2026-11-02T15:14Z

    Contract review PASS 6580628573 on head f68850fd; should-fixes filed #29448. Tier S, the seat lands it.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB

    Live acceptance after PR #29436 merged, read 2026-11-02T16:00Z — H1, H2 and H3 hold.

    • advisory_create ×5 from this cloud seat, 15:53–16:00 UTC, each its own stroke: 5 of 5 landed a draft advisory with an id in the run annotation (first run 41475893395). Earlier the same day advisory_report was refused 403 after its first landing, so five drafts in one window is the measurement that drafts are not under the per-account private-report limit.
    • advisory_collaborators_add ×5 on those drafts: 5 of 5 landed.
    • Minimum vulnerabilities shape: one npm entry with ecosystem and name only was accepted.
    • The run logs carry only op names, ids and states; no advisory text (checked on the first run's log).

    rest-channel.md can take its ✓ back for this row (carrier: #29448).


    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

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority: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