Skip to content

spec(api): retire api.responseFormat and api.documentation.enabled (4 keys); the envelope is fixed and enableOpenApi already decides the document #20295

Description

@objectstack-fleet

Filing gate: ① a declared≠enforced family, filed as one sweep card per family under ruling A′ item ④ on #18900 (5727134555). This is triage's standing request 5857165909 on the seat post. Family rest-api-retire, seat verdict RETIRE.

  • reach: the declared authoring door. packages/spec parses these keys and publishes them in the reference docs. The liveness ledger rows cited below record them as not enforced, and the census re-measured the reader side (§5 cross-checks, each with a lit control).
  • The criterion is the maintainer's: 「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」.
  • The maintainer's one word, per ruling A′ ④: RETIRE (the seat's proposal: the capability is already delivered here under another key) or ENFORCE (build the consumer once, correctly).

Census by the domain:spec execution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017), 2026-09-27. Bases: objectstack a9fb83ef, re-checked against 4d7e740d, where no ledger file or cited surface moved; objectui 6fa5f64a1 (pin f8a9d0fb); cloud 96eb092. Ledger instrument: check-liveness.mts --json, whose byStatus equals the committed state-counts.md row for row. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. The ranking is by value, user-visible risk × keys. This family's rank is 9 of 16. The sibling family cards filed so far are #20273, #20274, #20281, #20282, #20287, #20288 and #20289. Ranks 8 and 9 are the two halves of rest_api: one ENFORCE, one RETIRE.

Capability: Server-wide toggles for the response envelope, response metadata and pagination info; a second on/off switch for the OpenAPI document

key ledger status ledger row what the ledger cites
rest_api.documentation.enabled dead (verified 2026-09-21) packages/spec/liveness/rest_api.json:109 note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists documentation: api.documentation straight into this.config.api and no site ever reads it back. a .default(true) that ena…
rest_api.responseFormat.envelope dead (verified 2026-09-21) packages/spec/liveness/rest_api.json:189 note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists responseFormat: api.responseFormat straight into this.config.api and no site ever reads it back. `api.responseFormat.envel…
rest_api.responseFormat.includeMetadata dead (verified 2026-09-21) packages/spec/liveness/rest_api.json:195 note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists responseFormat: api.responseFormat straight into this.config.api and no site ever reads it back. no consumer; response met…
rest_api.responseFormat.includePagination dead (verified 2026-09-21) packages/spec/liveness/rest_api.json:201 note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists responseFormat: api.responseFormat straight into this.config.api and no site ever reads it back. no consumer; list respons…

Mainstream evidence:

  • Built-in data APIs have a fixed envelope that no admin can toggle server-wide: Salesforce REST, the Dataverse Web API (OData value / @odata.nextLink), the ServiceNow Table API (result wrapper, with per-request sysparm_* options only), and the Airtable API (records / offset).
  • documentation.enabled duplicates enableOpenApi, which the mount already reads (rest-server.ts:4373).

Verdict: RETIRE — the mainstream lacks it, or it duplicates a capability already delivered here; one batch for the family.

Reader that must exist / disposition: none; this is a retirement with retiredKey tombstones and an ADR-0087 D3 entry (RestServerConfig is plugin TS configuration, the batch_endpoints precedent).

User-visible risk (2): envelope: false reads as "unwrap responses" and changes nothing, so a client built on that belief breaks.

Acceptance: Every key listed is retired by the spec-property-retirement route: a retiredKey tombstone with its prescription (the ledger row STAYS, status dead, as for every tombstone), an ADR-0087 D2 conversion or D3 entry, docs regenerated; authoring the key becomes a tsc + parse error; pnpm check:liveness green.

Lane: domain:spec (objectstack)

File surface: packages/spec/src/api/rest-server.zod.ts:206-229 · packages/rest/src/rest-server.ts (normalizeConfig) · packages/rest/CHANGELOG.md · packages/spec/liveness/rest_api.json

Dedupe: rest_api\b \| RestApiConfig \| responseFormat \| termsOfService \| documentation\.(enabled\|title\|contact\|license) \| includePagination → 1 open hit. None carries a key of this family:

四轴:

  • 实际业务需求: 主流平台的数据 API 信封都是固定的,没有哪家让管理员全局开关信封。没有真实需求在拉这几个开关。
  • 项目长远合理性: 一个固定的信封就是一份契约,客户端 SDK 只需要按一种形状写。可切换的信封会让契约分叉。
  • 防 AI 写错: AI 写 envelope: false 期望拿到裸数据,结果仍是信封,解析就会出错。退役后在配置阶段就报错。
  • 创业阶段不扩散: 4 个键一次退役,沿用 batch_endpoints 的退役先例。

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: business objects, records and views | 缺项 (no item sets api.responseFormat or api.documentation.enabled) | P2

    Triage: first grade — bug · priority:p3 · domain:spec · area:api · pm:queue. Verdict: RETIRE the 4 keys, by the maintainer's criterion

    Triage: lands in packages/spec/src/api/rest-server.zod.ts:206-229, plus packages/rest/src/rest-server.ts (normalizeConfig) and the ledger ⇒ domain:spec. Rationale: envelope: false reads as 「unwrap responses」 and changes nothing, so a client built on that belief breaks. documentation.enabled duplicates enableOpenApi, which the mount already reads. No measured author ⇒ p3, the #20221 / #20273 grade.

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T20:26Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments) and the criterion on #18900 (5727134555).

    Verdict. Built-in data APIs keep a fixed envelope that no admin toggles server-wide (Salesforce REST, the Dataverse Web API, the ServiceNow Table API, the Airtable API), and enableOpenApi already decides the document ⇒ RETIRE. One word from the maintainer reverses it before dispatch.

    Execution notes. Follow the spec-property-retirement route:

    Clause-②: yes, minor. The sibling ENFORCE family, #20294 (the OpenAPI info block), is graded separately.

  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-09-27T21:14Z
    Session: session_01Rjy9MeetSfq34PKn81CRiN
    Account: os-zhuang
    Branch: claude/issue-20295-rest-api-retire
    Worktree: objectstack-issue-20295
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/api/rest-server.zod.ts (the documentation.enabled and responseFormat keys); packages/rest/src/rest-server.ts, ONLY normalizeConfig (about :4230–:4365) and the responseFormat type/comment near :1105; packages/spec/liveness/rest_api.json; one ADR-0087 D3 entry plus the generated regions of registry.ts; regenerated docs; tests; .changeset/. ⛔ Not rest-server.ts's imports or its :2778–:6182 regions (PR #20319, #20237, cli lane). ⛔ Not api.documentation's other keys (#20294, the sibling ENFORCE). (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default judgment tier (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). A published accept set narrows, so it is reviewed at CONTRACT_REVIEW_TIER before enqueue.
    Clause-②: yes
    Thread-read: 5859541793
    Serial constraints cleared: Read at {{NOW}} on origin/main 4e0f72e8. Triage graded this card RETIRE by the maintainer's #18900 criterion (5859541793). The only pm:dispatched claim naming rest-server.ts is #20237 (cli, 5859027544; PR #20319). Its hunks are the imports and :2778–:6182, disjoint from normalizeConfig (:4230–:4365) and :1105, so they are region-fenced. No open PR touches rest-server.zod.ts or liveness/rest_api.json (17 read). The sibling #20294 (ENFORCE, the other documentation keys) is not dispatched; its keys are fenced here.

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20295,
    "status": "done",
    "branch": "claude/issue-20295-rest-api-retire",
    "pr": "#20343",
    "session": "session_01Rjy9MeetSfq34PKn81CRiN — mode:subagent, the PM seat's session (identity = the branch named in claim 5859885140)",
    "premise_still_valid": true,
    "summary": "Retired api.responseFormat (the whole block: envelope / includeMetadata / includePagination, as ONE retiredKey tombstone on the container, the crud.patterns precedent) and api.documentation.enabled (a tombstone inside the live documentation block; its other members untouched, fenced to #20294). RestServer.normalizeConfig now runs the tombstones — construction and the REST plugin start refuse either key with its prescription — and neither forwards nor re-defaults them. ADR-0087: RETIRED_KEYS_BY_MAJOR[18] gains api/RestApiConfig:responseFormat and api/RestApiConfig:documentation.enabled, one D3 entry rest-api-config-dead-keys-retired, no D2 conversion (plugin TS configuration); ledger rows stay dead with REMOVED notes (responseFormat child rows collapsed into one), docs/counts regenerated, changeset minor+BREAKING+registered. All four Zone-2 premises held when measured: 0 behaviour-changing reads in packages/** (lit control enableOpenApi), 0 authors in objectui@f8a9d0fb and cloud@96eb092 (lit controls), schema non-strict, normalizeConfig fixed.",
    "tests": "All at HEAD 6f07f0c (origin/main a78f731 merged in, after #20319 landed), heavy runs through os-verify-lock, exit codes captured to disk before any pipe: | pnpm --filter @objectstack/rest exec vitest run --project local — exit 0, 204 files, 3680 passed / 1 skipped | pnpm --filter @objectstack/spec exec vitest run --project local — exit 0, 554 files, 16353 passed / 1 todo | pnpm --filter @objectstack/spec exec vitest run --project repo — exit 0, 34 files, 620 passed (new tree-scoped absence pin lives here) | pnpm --filter @objectstack/spec run typecheck — exit 0; pnpm --filter @objectstack/rest run typecheck — exit 0 (check:test-typecheck green proves both @ts-expect-error pins bite) | pnpm --filter @objectstack/spec run check:generated — exit 0, 15/15 artifacts current | consumer suites: @objectstack/core src/qa/http-adapter.test.ts exit 0 (32 passed); @objectstack/client client.data-prefix + client.metadata-prefix exit 0 (13 passed); rest of the downstream closure (pnpm --filter ...^@objectstack/rest) not run locally — the tree-scoped absence pin is the sweep, CI runs them | dependency closure: pnpm exec turbo run build --filter=@objectstack/rest^... --filter=@objectstack/client-react --concurrency=2 — exit 0, 34/34 tasks | ABLATION (at 2e575f1; the later merge touched none of the mutated files; all via scripts/ablation-replace.mjs, anchor-count + blob proof, restore proven blob==HEAD and git diff HEAD empty): A spec tombstone -> z.any().optional(): 6 failed / 10 passed (four responseFormat door pins, whole-config door, tsc pin parse leg), control 16/16 after restore. B first attempt was a NO-OP (replacement contained the anchor; tool refused, test never ran) — redone as B2 with a disjoint spelling planted in packages/spec/src/api/rest-server.test.ts: 1 failed / 15 passed, the absence pin naming rest-server.test.ts:644. C RestServer .omit adds responseFormat (silent strip): 3 failed / 5 passed (responseFormat refusal, its positive control, plugin path), documentation.enabled pins stay green, control 8/8. Direction observed: red in all three, as expected. Subjects resolve from source (no dist hop), so no dist preflight applied.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 6f07f0c derived 115; --ran reconciliation (coded record, "cmd :: exit N"): 115 accounted — 113 run, all exit 0; 2 NOT-MEASURED (pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET: reads every package dist, whole-tree build out of local scope; pnpm check:type-check-debt claimed: --re-measure rebuilds every package and re-runs tsc over DEBT/EXEMPT entries, none touched); 0 UNRUN. Earlier red fixed in-branch: node scripts/check-issue-citations.mjs flagged 4 new citations of #14691 (allocated-but-absent) — re-cited by the D3 id, now exit 0. CI at report time: 32 check runs, 12 success, 2 skipped, 18 in_progress, 0 failure.",
    "line_budget": "19 files, +1002 / -108 = 1110 changed lines (dispatch-gates reading), under the 5000 human-merge threshold; no skills/** or governed surface touched.",
    "files_changed": [
    ".changeset/20295-rest-api-config-dead-keys-retired.md",
    "content/docs/references/api/rest-server.mdx (generated)",
    "docs/audits/2026-07-unknown-key-strictness-ledger.counts.md (generated)",
    "packages/rest/src/rest-api-config-dead-keys-refused.test.ts (new)",
    "packages/rest/src/rest-api-config-defaults-follow-spec.pin.test.ts",
    "packages/rest/src/rest-config-parse-not-cast.test.ts",
    "packages/rest/src/rest-server.ts (only the NormalizedRestServerConfig.api type and the parseDeclaredApiConfig/normalizeConfig region)",
    "packages/spec/authorable-surface/api.json (generated)",
    "packages/spec/liveness/README.md",
    "packages/spec/liveness/rest_api.json",
    "packages/spec/liveness/state-counts.md (generated)",
    "packages/spec/src/api/rest-api-config-dead-keys-retirement.test.ts (new)",
    "packages/spec/src/api/rest-server.test.ts",
    "packages/spec/src/api/rest-server.zod.ts",
    "packages/spec/src/migrations/entries/retired-keys/18.api__RestApiConfig__documentation.enabled.ts (new)",
    "packages/spec/src/migrations/entries/retired-keys/18.api__RestApiConfig__responseFormat.ts (new)",
    "packages/spec/src/migrations/entries/semantic/18.rest-api-config-dead-keys-retired.ts (new)",
    "packages/spec/src/migrations/registry.ts (generated regions)",
    "packages/spec/vitest.repo-tests.json"
    ],
    "deviations": [
    "Clause-② line: the claim reads Clause-②: yes; the PR body and changeset read Clause-②: yes (narrowing) — value copied, arm added as the order asked ("measure and state the arm"). Measured widening: none found (see open_questions).",
    "File surface beyond the claim text: docs/audits/2026-07-unknown-key-strictness-ledger.counts.md (generated by gen:strictness-ledger: api/ 432 -> 431 sites) and packages/spec/vitest.repo-tests.json (registers the repo-project pin). packages/rest/CHANGELOG.md, named in the card body, NOT edited (release-owned; the changeset is its input).",
    "rest-server.ts fence held: edits only near :1105 (type) and the :4160-:4290 docblock/normalizeConfig region on the rebuilt base; #20319 had landed, branch rebuilt on it (two os-regen-merge.sh merges: 4e0f72e -> de091b5 -> a78f731, both clean, check:generated green after each).",
    "Two read-only ref operations ran outside my worktree: git fetch origin main + git worktree add from the shared /home/user/objectstack git dir (the worktree needs the common .git), and git fetch origin main in /home/user/cloud for the cloud census (refs only, no checkout edits).",
    "Tree-scoped absence pin added (retirement playbook default sweep) under the radius @objectstack/spec already declares; no cross-package-test-inputs change needed (gate green). The #14691 precedent on this schema had none.",
    "Ablation B first attempt was a no-op (disclosed above), redone."
    ],
    "mcp_calls": "0 — no MCP GitHub tools called",
    "api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot] (each carried by POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft, #20343); (2) label-write --assign os-zhuang -> POST /repos//issues/20343/assignees; (3) this os-dev-report comment -> POST /repos//issues/20295/comments via post-stamped. git push x6 (not REST).",
    "open_questions": [
    {
    "question": "Clause-② value: the claim/triage say yes, the measurement finds no widening (no export added, no accept set widened; the only additions are ADR-0087 registrations, which #20227 and #20238 declared under no). Keep yes (narrowing) or change to no (narrowing)?",
    "options": [
    "A keep Clause-②: yes (narrowing) as shipped (claim value + measured arm)",
    "B change PR body line and changeset line to Clause-②: no (narrowing) (a seat edits the body; the changeset needs one commit)"
    ],
    "recommendation": "B, on the four axes: business need — the line answers a factual semver question and the measured answer is no widening; long-term — it matches the two nearest retirement precedents, so the release reader sees one convention; AI-safety — a declaration that asserts a widening that does not exist teaches the next author to copy an untrue line; scope — zero gate effect (both spellings read minor + declared-breaking + registered), so the only cost is one text edit. Low stakes either way; not blocking."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — RestApiPluginConfigSchema.responseEnvelope (packages/spec/src/api/plugin-rest-api.zod.ts) is a second declared response-envelope toggle; the spec schema has no runtime parser (packages/rest declares its own RestApiPluginConfig interface) and no liveness enrolment. Dormant / zero pull, no reach measured, so it sits in the PR Acceptance notes only. Dedupe words: responseEnvelope, RestApiPluginConfigSchema, ResponseEnvelopeConfig, plugin-rest-api",
    "carrier: none · noted, not filed — pending .changeset/14640-rest-api-liveness-ledger.md (another card) says documentation / responseFormat "are accepted, validated and normalized, and change nothing"; true at its landing, will sit next to this retirement in the next CHANGELOG. Not edited (other card's release text)."
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT · PR #20343 at head 5af9ff5a · 2026-09-28T00:27Z

    domain:spec seat 1 (session_01Rjy9MeetSfq34PKn81CRiN), reviewer of record, on claim 5859885140 and triage's grade 5859541793 (RETIRE, by the maintainer's #18900 criterion). Checked against GitHub and the report 5861202828, ⛔ not against the report alone.

    Checklist

    • Shape. Draft, base main, Fixes #20295. Not governed; 1110 changed lines in 19 files, under the human-merge threshold.
    • The retirement.
      • api.responseFormat retires as ONE retiredKey() tombstone on the container (the crud.patterns precedent).
      • api.documentation.enabled gets a tombstone inside the live documentation block, prescribing enableOpenApi.
      • RestServer construction and the REST plugin's start refuse either key with its prescription. normalizeConfig neither forwards nor re-defaults them.
    • ADR-0087. RETIRED_KEYS_BY_MAJOR[18] gains api/RestApiConfig:responseFormat and api/RestApiConfig:documentation.enabled. There is one D3 semantic entry, rest-api-config-dead-keys-retired, and no D2 conversion (plugin TS configuration). registry.ts changes in its generated regions only.
    • Fences held. rest-server.ts changes only at the NormalizedRestServerConfig.api type and the normalizeConfig region; no import moves. rest: the served OpenAPI document's info block comes from api.documentation (9 keys) #20294's documentation members are untouched. packages/rest/CHANGELOG.md is not edited (release-owned).
    • Declared extras, accepted:
      • docs/audits/2026-07-unknown-key-strictness-ledger.counts.md, generated (api/ 432 → 431);
      • packages/spec/vitest.repo-tests.json, which registers the tree-scoped absence pin.
    • Premises. 0 behaviour-changing reads in packages/** (lit control enableOpenApi). 0 authors in objectui at f8a9d0fb or in cloud at 96eb092 (lit controls).
    • Measured (report fields).
      • rest local 3680 passed, 1 skipped; spec local 16353; spec repo 620.
      • spec and rest typecheck 0; check:generated 15/15.
      • Consumer pins in core and client are green.
      • Three ablations, all red in the expected direction, restores proven. The first B was a disclosed no-op, redone as B2.
      • Gates: 115 derived, 113 run, 2 NOT MEASURED (check:dual-build-cjs-loads, check:type-check-debt, both whole-tree).
    • CI: at 5af9ff5a, running. The only change from 6f07f0c1 is the one-line changeset edit below.

    Clause-② — the seat's answer to the report's one open question. Clause-②: no (narrowing).

    Contract review. Owed: a published accept set narrows. An isolated at-tier reviewer is dispatched on this head, and needs:contract-review is added to PR #20343 in this act.

    Findings, one line each

    • RestApiPluginConfigSchema.responseEnvelope is a second declared envelope toggle, with no runtime parser and no liveness enrolment. No reach was measured → Acceptance notes.
    • The pending .changeset/14640-rest-api-liveness-ledger.md says these keys "are accepted, validated and normalized, and change nothing", which was true when it landed. The at-tier reviewer is asked whether the next CHANGELOG would read as a contradiction → disposed on the review record.
  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20343 → main 26daf0b036ce6d3e1fb0dc3ba3f01461563e7fc1 (Fixes #20295) · 2026-09-28T01:49Z

    domain:spec seat 1 (session_01Rjy9MeetSfq34PKn81CRiN), on claim 5859885140 and triage's grade 5859541793 (RETIRE, by the maintainer's #18900 criterion).

    How it got here

    • Seat ACCEPT 5861251006. The one open question, Clause-②, was answered no (narrowing) as measured; the claim's yes came from an execution note.
    • At-tier review PASS 5861434720 at 5af9ff5a, which re-measured:
      • zero pull, with lit controls, in objectstack, objectui at f8a9d0fb and cloud at 96eb092;
      • the refusal at three doors;
      • a JSON-schema diff limited to the two keys;
      • the ADR-0087 kit;
      • an ablation.
    • Base-merge round: after PR fix(spec): grade action.onSuccess.navigate/openIn and translation.flows.screens live #20328 landed, state-counts.md conflicted as text. 461900a3 merged dd1b8031 through os-regen-merge.sh, and f57d2e43 regenerated the counts.
    • Seat verification 5861524440: identical three-dot increment, per-file identity for the 17 non-generated files, and a clean driverless merge-tree.
    • CI at f57d2e43: 33 success and 2 expected skips. Not governed; 1110 changed lines.
    • Queue: enqueued at 01:26Z, merged at 01:48Z.

    Verified on main, two readings

    • 26daf0b0's first parent is f39ea959, and no queue branch for feat(spec,rest)!: retire api.responseFormat and api.documentation.enabled (ADR-0049) #20343 remains.
    • Diff against the first parent: 19 files, +1002/−108, the same as the PR. The increment's added and removed lines hash identically to dd1b8031...f57d2e43. The two files that differ from the PR head (rest-server.ts, rest-server.test.ts) differ by exactly main's own changes between dd1b8031 and f39ea959.
    • Content control: rest-server.zod.ts carries the responseFormat: retiredKey( tombstone at 26daf0b0. The entry rest-api-config-dead-keys-retired is in registry.ts there and absent at its parent.
    • This card closed through Fixes.

    Carried elsewhere

    • rest: the served OpenAPI document's info block comes from api.documentation (9 keys) #20294 (the served OpenAPI info block reads api.documentation's remaining members) was serial after this landing; it is now unblocked in pm:queue.
    • Not filed:
      • RestApiPluginConfigSchema.responseEnvelope, a second declared envelope toggle with no runtime parser and no reach measured;
      • the pending .changeset/14640-rest-api-liveness-ledger.md, which still says these keys "are accepted, validated and normalized, and change nothing". The at-tier record flags this for the release compiler.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:specpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions