Skip to content

Report missing work queue pools with actionable recovery diagnostics - #71495

Merged
pelikhan merged 7 commits into
mainfrom
copilot/fix-work-queue-issue
Oct 24, 2026
Merged

pelikhan merged 7 commits into
mainfrom
copilot/fix-work-queue-issue

Conversation

Copilot AI commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The smoke dispatcher failed because smoke-work-queue was absent from the installed Policy, despite appearing in aw.json. Trusted controls masked both submission and dispatch failures as unclassified errors.

  • Routing diagnostics
    • Report work_queue_pool_not_installed for missing pools.
    • Distinguish missing profiles from compiler-disallowed worker routes.
  • Dispatch safety
    • Reject new missing-pool requests before deployment synchronization.
    • Preserve accepted-request recovery and installed Policy.
  • Recovery boundary
    • Explain that configuration changes do not update installed Policy.
    • Live recovery still requires an explicit administrator Policy update after quiescence; this PR does not mutate the live queue.
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title fix(work-queue): expose missing pool Policy recovery Oct 23, 2026
Copilot AI requested a review from pelikhan October 23, 2026 21:04
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot work queue pools should be auto installed on first use. admin need not required.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Comment on lines +1236 to +1239
if (!installedPolicy || installedPolicy.authorization !== "aw" || Object.hasOwn(installedPolicy.pools, operation.pool)) throw queueError("policy_invalid", "only new pools can be installed into an AW-managed Policy");
const policy = structuredClone(installedPolicy);
policy.pools[operation.pool] = structuredClone(operation.config);
validatePolicy(policy);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500: PoolInstall rejects reserved pool identities before lookups or mutation, and inserts an own data property with Object.defineProperty. Regression tests cover reserved keys and malformed/oversized identities.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot Review spec and TLA+ model

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor Author

@copilot work queue pools should be auto installed on first use. admin need not required.

Implemented first-use automatic installation for new AW-managed pools in dfe79a0; it does not require administrator seeding.

@copilot Review spec and TLA+ model

The normative spec already allowed additive PoolInstall, but QueueBootstrap covered only first-submit genesis. Added a bounded QueuePoolInstall model and documented its assumptions and focused checks in 0989b11.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft.

Commit pushed: 778022d

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Test Quality Sentinel · haiku45 · ~6 AIC · ⌖ ~9 AIC · ◇ ~3 AIC · ⊞ 9.2K · ◷

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Ponytail Reviewer

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Ponytail Reviewer · gpt56 · ~13 AIC · ⌖ ~6 AIC · ⊞ 14.8K · ◷

Generated by Ponytail Reviewer for #71495

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR adds 402 new lines of core business logic (more than the 100-line threshold) in pkg/ and work-queue JS, but it does not link an Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/71495-additive-first-use-work-queue-pool-installation.md. Review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

Decision found in the diff: this PR adds a new PoolInstall operation and pool_install intent. On first use, it adds one new compiler-approved, repository-scoped pool to an existing AW-managed Policy. The roles administrator, producer, and dispatcher can trigger it, and existing pools and the Policy epoch stay unchanged. Evidence: work_queue_pool_install.cjs, replay.go#installReplayPool, QueuePoolInstall.tla.

⚠️ Mismatch with the PR description: the PR body says live recovery "requires an explicit administrator Policy update" and that "this PR does not mutate the live queue". Commits dfe79a0 and 0989b11 add automatic first-use pool installation, which writes PoolInstall commits to the queue. Please update either the description or the scope. The draft ADR flags this as [TODO: verify].

📋 What to do next
  1. Review the draft ADR committed to your branch. It was generated from the PR diff.
  2. Complete the missing sections. Confirm the deciders, refine the rationale, and settle the scope mismatch above.
  3. Commit the finalized ADR to docs/adr/ on your branch.
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-71495: Additive First-Use Pool Installation for AW-Managed Work Queues

Once an ADR is linked in the PR body, this gate will re-run and check that the implementation matches the decision.

❓ Why ADRs Matter

ADRs keep a searchable, permanent record of why the codebase looks the way it does. This PR widens who can write Policy (producer and dispatcher can now add pools), so that reasoning should be recorded.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context: What is the problem? What forces are at play?
  • Decision: What did you decide? Why?
  • Alternatives Considered: What else could have been done?
  • Consequences: What are the trade-offs (positive and negative)?

ADRs are stored in docs/adr/ as Markdown files named by PR number.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · pi · opus55 · ~33 AIC · ⌖ ~41 AIC · ⊞ 1.9K · ◷
Comment /review to run again

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

Peek at saved memory (pr-code-quality-reviewer)
reviewed_at: 2026-10-24T04:49:31Z
review_event: REQUEST_CHANGES
top_themes: compiler-approval enforcement at replay boundary; dispatch mutating live Policy contrary to recovery contract
files_reviewed: actions/setup/js/work_queue_dispatch.cjs, actions/setup/js/work_queue_pool_install.cjs, actions/setup/js/work_queue_replay.cjs, pkg/workqueue/replay.go, pkg/workqueue/deployment.go, pkg/workqueue/types.go, specs/work-queue/QueuePoolInstall.tla
comment_count: 2

Note

This comment is managed by comment memory.

Expand the saved memory block to view or edit the persistent context for this thread.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt50 · ~4 AIC · ⌖ ~1 AIC · ⊞ 1.6K · ◷
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

The new additive pool path has two merge-blocking authorization problems: authoritative Go replay accepts compiler-unapproved worker routes from producer/dispatcher operations, and dispatch now mutates the live Policy despite the PR's stated explicit-administrator recovery boundary. See the inline comments for the required fixes.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt50 · ~4 AIC · ⌖ ~1 AIC · ⊞ 1.6K
Comment /review to run again

Comment thread pkg/workqueue/replay.go
return queueError("policy_invalid", "only new pools can be installed into an AW-managed Policy")
}
if _, exists := state.Policy.Pools[install.Pool]; exists {
return queueError("policy_invalid", "only new pools can be installed into an AW-managed Policy")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This replay path accepts arbitrary worker routes from any producer or dispatcher, so a direct pool_install request can add a workflow/ref/contract that was never compiler-approved; the allowlist check exists only in the JS helper and is bypassed by replaying the ledger.

💡 Enforce approval at the authoritative boundary

installReplayPool checks only effect scope and principal, then commits the supplied profile. Since PoolInstall is explicitly permitted for producer/dispatcher roles, callers can submit a crafted operation without going through installWorkQueuePools. Enforce a verifiable approval (for example, a signed/proposal binding) during replay, or narrow this operation to an authority that can establish compiler approval; otherwise the stated worker-route safety is not enforced by the ledger.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500. Both Go and JS replay now require each profile to exactly match existing same-named worker authority or an available registered Deployment revision. Raw operations cannot introduce a workflow, ref, contract, or broader authority; regression tests bypass the helper.

const stableId = requestIdForIntent(trustedContext, message.intent_id);
const prior = latest.projection.requests.get(stableId);
if (!prior) {
latest = (await installWorkQueuePools(configured, trustedContext, [requested.pool])) || latest;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dispatch path now appends a live PoolInstall commit for a missing pool, contradicting the PR contract that new missing-pool requests are rejected and that live recovery still requires an explicit administrator Policy update; a worker-facing dispatch therefore mutates installed Policy before deployment synchronization.

💡 Preserve the documented recovery boundary

installWorkQueuePools publishes a durable Policy mutation and loadQueue immediately observes it. That is not merely diagnostic handling and can change the active queue configuration from a dispatch request. Either reject the missing pool before synchronization as described, or redesign the authorization and documentation around an explicit administrator-approved installation; do not silently turn dispatch into Policy administration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maintainer's first-use auto-install requirement supersedes the original diagnostics-only scope. 94d6500 documents that explicitly in the specification and ADR. Installation remains non-administrator and additive, but replay now prevents introducing new worker authority; existing-pool/economic Policy changes still require administrator authorization and quiescence.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on a prototype-pollution gap in the new pool-install path.

📋 Key Themes & Highlights

Key Themes

  • Prototype pollution risk: work_queue_replay.cjs:1238 assigns to policy.pools[operation.pool] on a plain object. identity() validates length/control-chars but not __proto__/constructor/prototype, matching the GitHub Advanced Security finding already on this PR. The same bracket-lookup pattern in work_queue_pool_install.cjs:22 (proposal.pools[poolName]) silently resolves __proto__ to Object.prototype (truthy), bypassing the intended work_queue_pool_not_installed guard.
  • Test coverage gap: the new work_queue_dispatch.test.cjs suite thoroughly covers policy-shape rejections (foreign effect scope, mismatched proposals, duplicate installs) but has no case for a malicious pool name. Given this PR's own framing — "Trusted controls masked...failures as unclassified errors" — a __proto__ pool name would currently escape detection with a confusing crash rather than a clean work_queue_pool_not_installed/ledger_invalid error.

Positive Highlights

  • ✅ Clear separation of the new PoolInstall operation from Policy, with matching validation in both the JS (work_queue_replay.cjs) and Go (pkg/workqueue/replay.go) replay engines.
  • ✅ Good safety invariants already enforced and tested: epoch preservation, no mutation of existing pools, repository-scoped effect_scope/no fixed principal on auto-installed profiles.
  • ✅ Error code renaming (work_queue_profile_not_approved → work_queue_pool_not_installed for the missing-pool case) is a real diagnostic improvement matching the PR's stated goal, and the corresponding work_queue_logging.cjs explanations are clear and actionable.

Note: the pre-fetched diff (1750 lines) appeared complete for all 38 changed files, so no files were skipped for size reasons.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · ~99 AIC · ⌖ ~16 AIC · ⊞ 10.2K
Comment /matt to run again

Comment thread actions/setup/js/work_queue_replay.cjs Outdated
const installedPolicy = state.policy;
if (!installedPolicy || installedPolicy.authorization !== "aw" || Object.hasOwn(installedPolicy.pools, operation.pool)) throw queueError("policy_invalid", "only new pools can be installed into an AW-managed Policy");
const policy = structuredClone(installedPolicy);
policy.pools[operation.pool] = structuredClone(operation.config);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] policy.pools[operation.pool] = structuredClone(operation.config) writes to a plain object using an attacker-influenced key. identity() rejects empty/control-char strings but allows __proto__, constructor, or prototype, so a crafted PoolInstall operation can pollute Object.prototype here.

💡 Suggested fix

Use a null-prototype pools map (or reject dangerous keys explicitly) before assignment, matching the guard already needed in work_queue_pool_install.cjs:

if (["__proto__", "constructor", "prototype"].includes(operation.pool)) throw queueError("policy_invalid", "invalid pool name");
// or construct policy.pools with Object.create(null) at genesis

This is the root cause GitHub Advanced Security already flagged on this line — the comment was truncated, so I am restating it with a concrete repro: proposal.pools["__proto__"] resolves to Object.prototype (truthy), which also defeats the "pool is absent" check in poolFromProposal (work_queue_pool_install.cjs:22). No regression test currently exercises a __proto__/constructor pool name.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500: reserved pool keys are rejected before access, and replay uses Object.defineProperty instead of the prototype-sensitive assignment. JS and Go regression tests cover malicious pool identities.

for (const [name, pool] of Object.entries(state.policy.pools)) {
if (canonical(proposal.pools[name]) !== canonical(pool)) throw queueError("work_queue_policy_proposal_mismatch", "automatic pool installation cannot change an installed pool");
}
const pool = proposal.pools[poolName];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] proposal.pools[poolName] is a bracket lookup on a plain object. If poolName is __proto__, this resolves to Object.prototype (truthy), so the if (!pool) throw ... missing-pool guard on the next line is bypassed and pool.profiles is undefined, producing a confusing downstream crash instead of the intended work_queue_pool_not_installed error.

💡 Suggested fix
const pool = Object.hasOwn(proposal.pools, poolName) ? proposal.pools[poolName] : undefined;
if (!pool) throw queueError("work_queue_pool_not_installed", "the requested pool is absent from the compiled queue policy");

Same pattern applies to state.policy.pools[poolName] at line 71 and the Object.entries(state.policy.pools) loop at line 19 — entries() is safe, but any future bracket access on these maps should use Object.hasOwn first.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500. Proposal lookup now requires an own pool property, and reserved pool identities are rejected before any map access. Missing proposals retain the classified missing-pool diagnostic.

@@ -1908,6 +1909,110 @@ describe("native queue launch fencing and conservative recovery", () => {
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Good coverage for the happy-path auto-install and the policy-mutation rejection, but there is no test exercising a malicious/edge-case pool name (__proto__, constructor, empty after normalization, very long UTF-8). Given the prototype-pollution risk flagged on work_queue_replay.cjs:1238 and work_queue_pool_install.cjs:22, a regression test here (or in work_queue_replay.test.cjs) would catch the real vulnerability class, not just the policy-shape checks already covered.

💡 Suggested test
it("rejects pool installation with a __proto__ pool name", async () => {
  const { fixture, options } = setup({ granted: false, configurePolicy: policy => { policy.authorization = "aw"; } });
  const proposal = structuredClone(fixture.policy);
  proposal.pools.__proto__ = structuredClone(proposal.pools.default);
  const trusted = { ...fixture.dispatcher, authenticated: true, roles: ["dispatcher"] };
  await expect(installWorkQueuePools({ ...options, policyProposal: proposal }, trusted, ["__proto__"]))
    .rejects.toThrow();
  expect(Object.prototype).not.toHaveProperty("profiles");
});

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added regression coverage in 94d6500 for proto, constructor, prototype, empty identities, oversized UTF-8 names, and valid 256-byte ASCII/UTF-8 names. Tests cover both helper publication and raw replay, including preservation of existing state on rejection.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved runtime, installation, recovery, and conformance failures prevent reliable queue operation.

8 open issues
What changed in this PR

Extends gh-aw’s native work queue with additive first-use pool installation and actionable routing diagnostics, supported by Go and JavaScript replay implementations.

Changes:

  • Add PoolInstall requests, schemas, authorization, and epoch-preserving replay.
  • Integrate pool installation into submission and dispatch, with clearer routing diagnostics.
  • Expand regression fixtures, formal models, and operator documentation.
File Description
specs/​work-queue/​transactions.tsp Define pool-install operations and request kind.
specs/​work-queue/​README.md Document additive installation and model checks.
specs/​work-queue/​QueuePoolInstall.tla Model installation safety and authority boundaries.
specs/​work-queue/​PoolInstallUnauthorized.cfg Check unauthorized publication is blocked.
specs/​work-queue/​PoolInstallProducerWitness.cfg Demonstrate authorized producer installation.
specs/​work-queue/​PoolInstallInvalidRoute.cfg Check immutable-route requirements.
specs/​work-queue/​PoolInstallInvalidProposal.cfg Check existing Policy preservation.
specs/​work-queue/​PoolInstallInvalidPool.cfg Check invalid pool rejection.
specs/​work-queue/​PoolInstall.cfg Configure the valid installation model.
specs/​work-queue/​fixtures/​generate_prefix.py Generate AW-authorized pool-install fixture coverage.
specs/​work-queue/​fixtures/​canonical-prefix.json Record the expanded canonical transaction fixture.
specs/​work-queue/​check.sh Register pool-install model checks.
pkg/​workqueue/​types.go Add the pool-install operation type.
pkg/​workqueue/​schema/​RequestParameters.json Allow pool installation in request parameters.
pkg/​workqueue/​schema/​QueueRequest.json Validate pool-install requests.
pkg/​workqueue/​schema/​QueueOperation.json Include pool installation in operation validation.
pkg/​workqueue/​schema/​QueueCommit.json Accept pool-install transactions.
pkg/​workqueue/​schema/​PoolInstallOperation.json Define standalone pool-install validation.
pkg/​workqueue/​schema/​OperationsParameters.json Extend operation-based parameters.
pkg/​workqueue/​request.go Authorize pool-install request roles.
pkg/​workqueue/​replay.go Replay additive pools while preserving Policy state.
pkg/​workqueue/​replay_test.go Test preservation and unsafe installation rejection.
pkg/​workqueue/​policy.go Add operation permissions and request matching.
pkg/​workqueue/​policy_permitted_test.go Cover pool-install role permissions.
pkg/​workqueue/​fixtures_test.go Expect thirteen shared operation kinds.
pkg/​workqueue/​deployment.go Initialize deployments for selected pools.
docs/​src/​content/​docs/​specs/​work-queue-specification.md Specify additive installation constraints.
docs/​src/​content/​docs/​reference/​work-queue.md Explain first-use installation behavior.
docs/​src/​content/​docs/​patterns/​workqueue-ops.md Describe installation and administrator update boundaries.
actions/​setup/​js/​work_queue_replay.cjs Validate and replay pool-install operations.
actions/​setup/​js/​work_queue_pool_install.cjs Verify proposals and publish additive installations.
actions/​setup/​js/​work_queue_policy.cjs Authorize JavaScript pool-install requests.
actions/​setup/​js/​work_queue_logging.test.cjs Cover routing diagnostics and data privacy.
actions/​setup/​js/​work_queue_logging.cjs Explain missing pools and unapproved routes.
actions/​setup/​js/​work_queue_dispatch.test.cjs Exercise installation and preservation behavior.
actions/​setup/​js/​work_queue_dispatch.cjs Install missing pools before new queue requests.
actions/​setup/​js/​work_queue_deployment.cjs Preserve existing deployments during pool initialization.
.github/​aw/​work-queue.md Update internal queue-operation guidance.

Review effort: Max

const { verifyWorkerRoute } = require("./work_queue_provisioning.cjs");
const { futurePolicy } = require("./work_queue_deployment.cjs");
const { approvedWorkerProfiles, synchronizeDeployments } = require("./work_queue_deployment_control.cjs");
const { installWorkQueuePools } = require("./work_queue_pool_install.cjs");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500: the module is included in both setup.sh deployment lists and SessionParserSources. Added explicit packaging regressions; setup bundle dependency tests pass.

const stableId = requestIdForIntent(trustedContext, message.intent_id);
const prior = latest.projection.requests.get(stableId);
if (!prior) {
latest = (await installWorkQueuePools(configured, trustedContext, [requested.pool])) || latest;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retained automatic installation as requested by the maintainer. 94d6500 reconciles the specification and ADR with that scope: producer/dispatcher first use may add a pool without quiescence, using only existing approved worker authority. Changing existing pools or scheduling economics still requires an administrator's quiescent Policy update.

Comment on lines +19 to +21
for (const [name, pool] of Object.entries(state.policy.pools)) {
if (canonical(proposal.pools[name]) !== canonical(pool)) throw queueError("work_queue_policy_proposal_mismatch", "automatic pool installation cannot change an installed pool");
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500. Proposal comparison uses stable worker boundaries rather than caller ref/contract, preserves installed entries, and copies an exact available approved profile for the new pool. Tests cover different bootstrap/caller revisions and subsequent verified deployment synchronization.

Comment on lines +26 to +29
for (const profile of Object.values(pool.profiles)) {
const name = profile.workflow.replace(/^\.github\/workflows\//, "").replace(/\.(?:lock\.yml|yml|yaml)$/, "");
if (profile.effect_scope !== state.repository || Object.hasOwn(profile, "principal") || !workflows.has(name) || !contexts.has(name))
throw queueError("work_queue_worker_not_compiler_approved", "new pool routes must be compiler-approved and preserve repository-scoped AW authority");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500. Installation resolves profiles against repository-wide installed/registered worker authority and verifies the selected routes, rather than requiring every profile in the caller-local target list. Submission and dispatch still enforce caller-local targets. Added a caller approved for only one of multiple installed workers.

Comment on lines +1242 to +1243
state.policy = policy;
initializeDeployments(state, [operation.pool]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500. Both consumers reconstruct Policy through PoolInstall at the accepted request's ledger position. Checkpoints retain complete Policy/PoolInstall history in JS and Go, while existing checkpoint hashes remain recoverable. Tests cover accepted submit retry and claim-scoped dispatch_next readback after later epochs and repeated checkpoints.

"mode": "weighted-priority", "class_weights": [8, 4, 2, 1, 1],
"accounting_weights": {"": 1},
"producers": {"1001": {"pools": ["default"], "priorities": [1, 2, 3, 4, 5], "fairness_keys": [""]}},
"authorization": "aw", "producers": {},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500: the worker-child generator removes inherited authorization before adding entitlement-based cases. Regeneration matches the committed fixture without a JSON diff; worker-child JS/Go tests pass.

Comment on lines +201 to +203
append("dispatcher", "pool-install", "pool_install", 5300, [{
"kind": "PoolInstall", "pool": "smoke", "config": new_pool,
}], reason="grants_paused")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500: the JavaScript conformance assertion and test title now expect thirteen operation kinds. Also corrected the canonical fixture's later Policy so its PoolInstall inherits already authorized worker authority.


for (const [poolName, pool] of candidates) {
const operation = { kind: "PoolInstall", pool: poolName, config: pool };
const request = requestForIntent(trustedContext, `pool-install:${poolName}`, "pool_install", { operations: [operation] });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94d6500 using pool-install:${digest(poolName)}. Regression tests successfully install 256-byte ASCII and UTF-8 names, keep request IDs bounded, and verify retry idempotence.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot address the following outstanding work in one pass:

  1. Update this branch with the latest main using make merge-main, resolving any conflicts and preserving the intended changes.
  2. Review (actions/setup/js/work_queue_replay.cjs:1239): policy.pools[operation.pool] = structuredClone(operation.config) uses a plain-object assignment with an attacker-chosen key. identity() only rejects empty/control-character strings, so a PoolInstall whose pool is proto invokes the Object.prototype setter: it replaces the prototype of policy.pools instead of creating an own property. The subsequent validatePolicy(policy) iterates Object.entries(policy.pools) and therefore never validates the injected pool at all, so val... - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  3. Review (pkg/workqueue/replay.go:365): This replay path accepts arbitrary worker routes from any producer or dispatcher, so a direct pool_install request can add a workflow/ref/contract that was never compiler-approved; the allowlist check exists only in the JS helper and is bypassed by replaying the ledger. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  4. Review (actions/setup/js/work_queue_dispatch.cjs:467): This dispatch path now appends a live PoolInstall commit for a missing pool, contradicting the PR contract that new missing-pool requests are rejected and that live recovery still requires an explicit administrator Policy update; a worker-facing dispatch therefore mutates installed Policy before deployment synchronization. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  5. Review (actions/setup/js/work_queue_replay.cjs:1238): [/diagnosing-bugs] policy.pools[operation.pool] = structuredClone(operation.config) writes to a plain object using an attacker-influenced key. identity() rejects empty/control-char strings but allows __proto__, constructor, or prototype, so a crafted PoolInstall operation can pollute Object.prototype here. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  6. Review (actions/setup/js/work_queue_pool_install.cjs:22): [/diagnosing-bugs] proposal.pools[poolName] is a bracket lookup on a plain object. If poolName is __proto__, this resolves to Object.prototype (truthy), so the if (!pool) throw ... missing-pool guard on the next line is bypassed and pool.profiles is undefined, producing a confusing downstream crash instead of the intended work_queue_pool_not_installed error. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  7. Review (actions/setup/js/work_queue_dispatch.test.cjs:1911): [/tdd] Good coverage for the happy-path auto-install and the policy-mutation rejection, but there is no test exercising a malicious/edge-case pool name (__proto__, constructor, empty after normalization, very long UTF-8). Given the prototype-pollution risk flagged on work_queue_replay.cjs:1238 and work_queue_pool_install.cjs:22, a regression test here (or in work_queue_replay.test.cjs) would catch the real vulnerability class, not just the policy-shape checks already covered. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  8. Review (actions/setup/js/work_queue_dispatch.cjs:29): [P1] Both deployment lists in actions/setup/setup.sh and the SessionParserSources embed list omit work_queue_pool_install.cjs. This top-level import therefore fails with MODULE_NOT_FOUND in the copied runtime bundles, blocking queue controls even for existing pools. Add the new module alongside the other queue dependencies in all three lists. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  9. Review (actions/setup/js/work_queue_dispatch.cjs:467): [P1] This call automatically appends PoolInstall for producers and dispatchers without quiescence; the submit path at line 593 does the same. That contradicts the PR description's explicit missing-pool rejection and administrator-only recovery boundary. Remove automatic publication if that boundary is required, or update the PR scope to explicitly describe and justify non-administrator installation. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  10. Review (actions/setup/js/work_queue_pool_install.cjs:21): [P1] Compiled AW proposals pin each profile to ${{ github.sha }}, while installed Policy retains its bootstrap refs after Deployment updates. Adding a pool from a later revision therefore fails this full-pool comparison with work_queue_policy_proposal_mismatch, even when scheduling and authority are unchanged. Compare stable pool/profile boundaries while preserving installed entries, and cover different bootstrap and caller revisions. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  11. Review (actions/setup/js/work_queue_pool_install.cjs:29): [P2] The compiler copies all repository AW workers into each pool, but the caller's protected allowlist contains only its dispatch targets. A caller approved only for beta cannot install a pool that also contains alpha, even when it only submits to beta (pkg/workflow/work_queue_repo_config_test.go:59–81). Validate installation against repository-wide verified worker eligibility while retaining caller-local checks for actual submission and dispatch. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  12. Review (actions/setup/js/work_queue_replay.cjs:1243): [P1] PoolInstall changes the effective Policy without advancing its epoch, but acceptedSubmissionParameters() and verifyClaimQueueControl() still select only that epoch's original Policy operation. Retrying an accepted submission or verifying a claim-scoped dispatch_next for the added pool therefore throws policy_invalid because the reconstructed policy lacks the pool. Reconstruct Policy through the relevant PoolInstall operations at the accepted request's position, including after later epochs, and cover retry/readback recovery. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  13. Review (specs/work-queue/fixtures/generate_prefix.py:36): [P1] The worker-child fixture generator clones this Policy and adds producer entitlements without removing authorization: \"aw\". Its output no longer matches the committed fixture, and regenerating it produces policies rejected by validatePolicy because AW authorization forbids producer entitlements. Explicitly remove authorization in actions/setup/js/work_queue_worker_child_fixture_generator.cjs to preserve its entitlement-based cases, then regenerate work_queue_worker_child_fixtures.json. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  14. Review (specs/work-queue/fixtures/generate_prefix.py:203): [P1] This adds a thirteenth operation kind to the shared fixture, but actions/setup/js/work_queue_conformance_checks.cjs:120–123 still asserts exactly 12. Update the JavaScript count and test title alongside the Go expectation so the regenerated fixture does not fail conformance checks. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)
  15. Review (actions/setup/js/work_queue_pool_install.cjs:59): [P2] Pool names support 256 UTF-8 bytes, but requestForIntent validates the entire intent ID against the same limit. Prefixing the name with pool-install: makes otherwise valid 244–256-byte names fail with ledger_invalid. Import the existing codec digest helper and derive a bounded, stable ID such as pool-install:${digest(poolName)}. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)

Push the necessary fixes, reply to each listed review thread and resolve it when addressed. Ignore feedback already answered or resolved. Use the pr-finisher skill and stop when only human review or CI remains; do not trigger CI.

Sous-chef head: 778022d6d58e66ab819827e87354992bc327db44
Sous-chef work: 07c9f7b8b675110ecf2a6c0df28993eab131c3bf89f86466357226551236d6eb 4e1fcc9d29f56be477fb5a32d4a6d5359333a1c468768e029494fcbbe3d2285a 5f6219ae1dc1710f80a89d01ed73e7c6488683e7e9af8f13894a58aef110cf6d 721bc9da5b027a4ea58c43c66478f98aa9fba27b55b4181d6f72c4137f886581 8688567423b2374ed5eb339b7e98ddf82ffb9bc1c13dc287c8f10ffc366f5743 8b06633ecc3f3ce95d97133c83faa3c2cad9f7517cc6b87eb37fc9fbb92a97a9 99fa897cfdc6fa2dad0a9c1a716f8eaf9b51a55ca4670481c422e835805914f2 a02f2975d0859bb78d676f995ebae6c286c87a7f663236e80b3b16b74fb1438c a1d46bb7aaa3fb69c6b2a9fe9dfb6ae74f6c826e41cc8c22ba8e9d084d0694e5 ae54e4f0fd382226ac4438549bcea59a0cd4969b388f6b9f0560130f4704fcec d592ad3d53282b4436c6818789ac65fac0b9e93f1c0c6a254d655e97df1f5ee6 d66fb5032a568d334b0cec4331b7b0e0e362443e5450d8d6913c577f81ee33bf e79889fd2efda4cdf54775e3cde2d29303b8bd329140a91e165bfd21d9090f88 f01c80d5878b63b4990864aa6cb01b1924c05502c7995cd7d3fd67bcadb22ca5
Sous-chef state: 43e68ed455e645b2ac5275a7dcd672b222f115216b96b1a0317401c2b7df8f58

Generated by 👨‍🍳 PR Sous Chef · pi · haiku45 · ~8 AIC · ⌖ ~8 AIC · ⊞ 1.1K · ◷
Comment /souschef to run again

…e-issue

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…recovery

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address the following outstanding work in one pass:

  1. Update this branch with the latest main using make merge-main, resolving any conflicts and preserving the intended changes....

Merged latest main with make merge-main in c34dfee and pushed the fixes in 94d6500. All 14 listed review threads have a direct reply. First-use installation remains non-administrator, as requested; the specification/ADR now explicitly describe that scope, and replay prevents introducing new worker authority.

Validation: full Go workqueue/setup suites, final impacted Go tests, 312 impacted JS tests, documentation checks, seven TLC configurations, and secret scanning passed. The final gate remains blocked by unchanged custom-linter findings in checkpoint.go. Two negative-control benchmark timeouts reproduce with the pre-fix runtime. CodeQL timed out and instructed against retrying; read-only security/code reviews found no remaining high-confidence issues.

No CI was triggered. The available tools support replies but not thread resolution or PR-body edits, so the 14 threads still need resolution and the outdated administrator-only PR description needs reconciliation. Human review and CI verification of the pushed HEAD remain required.

Comment on lines +1260 to +1264
validatePolicy(policy);
if (Object.values(operation.config.profiles).some(profile => profile.effect_scope !== state.repository || Object.hasOwn(profile, "principal")))
throw queueError("policy_invalid", "automatically installed AW pools must preserve repository-scoped authority");
for (const [name, profile] of Object.entries(operation.config.profiles))
if (!approvedPoolProfile(state, name, profile)) throw queueError("policy_invalid", "pool profiles must match installed or registered worker revision authority");
const stableId = requestIdForIntent(trustedContext, message.intent_id);
const prior = latest.projection.requests.get(stableId);
if (!prior) {
latest = (await installWorkQueuePools(configured, trustedContext, [requested.pool])) || latest;
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot address the following outstanding work in one pass:

  1. Update this branch with the latest main using make merge-main, resolving any conflicts and preserving the intended changes.
  2. Review (actions/setup/js/work_queue_replay.cjs:1264): The replay guard for PoolInstall only constrains the new pool's profiles (effect scope, absence of principal, and match against an installed/registered worker revision). Every other field of operation.config — most importantly allowed_repositories — is accepted verbatim from the publisher. Since pool_install is now permitted for the producer and dispatcher roles (not just administrator), a publisher that writes a PoolInstall operation directly to the ledger (bypassing work_queue_pool_install.cjs, whose proposal comparison is the only place the compiled aw.json is consulted) can install a pool whose allowed_repositories names arbitrary repositories. That allowlist is a queue-wide authority boundary: applyObservation authorizes observations with Object.values(state.policy.pools).some(pool =\u003e pool.allowed_repositories.includes(...)), and work_queue_control_adapter.cjs derives the dependency-read repository scope from the union over all pools. The result is a non-administrator widening of the dependency/observation allowlist that previously required an administrator policy request on a quiescent queue. Replay should additionally require the new pool's non-profile authority fields (at minimum allowed_repositories) to be a subset of what installed pools already authorize.

General fix: during replay of PoolInstall, enforce that authority-bearing non-profile fields in the new pool config cannot expand existing authority. At minimum, require operation.config.allowed_repositories to be a subset of repositories already authorized by installed pools.

Best concrete fix in this file:

  • In actions/setup/js/work_queue_replay.cjs, inside case "PoolInstall" (around current lines 1260–1264), add a subset check after validatePolicy(policy) and before mutating state.
  • Compute the set of currently authorized repositories from installedPolicy.pools (the pre-install policy snapshot), then verify every repository in operation.config.allowed_repositories is already present.
  • If not, throw queueError("policy_invalid", "...").
  • Keep existing profile checks unchanged.

This preserves functionality for legitimate installs while preventing non-admin widening of repository authority during replay. - #71495 (comment)
3. Review (actions/setup/js/work_queue_dispatch.cjs:465): Excessive agency risk: actions/setup/js/work_queue_dispatch.cjs automatically calls installWorkQueuePools on first use of a missing pool (e.g., requested.pool from an intent) before dispatch/submission proceeds. This triggers a repository-mutating publication path (actions/setup/js/work_queue_pool_install.cjs publishes a PoolInstall request via publishWorkQueueRequest) without an independent approval step tied to the specific pool/arguments. In an agent-driven dispatcher/producer workflow, an attacker-controlled or prompt-injected intent could steer the automation to add arbitrary new pool names (policy growth/state change) using the workflow's GitHub credentials.

The safest minimal fix is to remove implicit pool installation during dispatch intent handling and fail closed when the requested pool is missing. That preserves normal dispatch behavior for existing pools while preventing this endpoint from autonomously creating new pools from untrusted/semi-trusted input.

Best single fix in this file/region:

  • In dispatchQueueIntent (around lines 464–469), replace the auto-install/synchronize block with a hard failure when prior is absent and the requested pool is not already known in projection.
  • Throw a deterministic error (e.g., work_queue_pool_not_found) so callers can route pool creation through an explicit, separately approved workflow.
  • No new imports or dependencies are needed. - Report missing work queue pools with actionable recovery diagnostics #71495 (comment)

Push the necessary fixes, reply to each listed review thread and resolve it when addressed. Ignore feedback already answered or resolved. Use the pr-finisher skill and stop when only human review or CI remains; do not trigger CI.

Sous-chef head: 94d6500
Sous-chef work: 20462820657e0821d0628cd08150602f0e5c757d5c657ef210a2407ff67d800a f900b9b0d8694438c625f8fe428590be0b4789686ebaa38dcd98bead6cc5e965
Sous-chef state: 6351c6ab6c5bb1851a5c2090d3a6d86b362363c8b8728b82051c5151d35d203a

Generated by 👨‍🍳 PR Sous Chef · pi · haiku45 · ~6 AIC · ⌖ ~10 AIC · ⊞ 1.1K · ◷
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address the following outstanding work in one pass:

  1. Update this branch with the latest main using make merge-main, resolving any conflicts and preserving the intended changes....

Addressed in 8786c98: JS and Go replay now reject PoolInstall repository allowlists that exceed installed authority, and ordinary submit/dispatch intents reject missing pools before deployment synchronization. Updated the bounded model and recovery docs as well.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot address the following outstanding work in one pass:

  1. Update this branch with the latest main using make merge-main, resolving any conflicts and preserving the intended changes.
  2. Review (actions/setup/js/work_queue_replay.cjs:1266): The replay guard for PoolInstall only constrains the new pool's profiles (effect scope, absence of principal, and match against an installed/registered worker revision). Every other field of operation.config — most importantly allowed_repositories — is accepted verbatim from the publisher. Since pool_install is now permitted for the producer and dispatcher roles (not just administrator), a publisher that writes a PoolInstall operation directly to the ledger (bypassing work_queue_pool_install.cjs, whose proposal comparison is the only place the compiled aw.json is consulted) can install a pool whose allowed_repositories names arbitrary repositories. That allowlist is a queue-wide authority boundary: applyObservation authorizes observations with Object.values(state.policy.pools).some(pool => pool.allowed_repositories.includes(...)), and work_queue_control_adapter.cjs derives the dependency-read repository scope from the union over all pools. The result is a non-administrator widening of the dependency/observation allowlist that previously required an administrator policy request on a quiescent queue. Replay should additionally require the new pool's non-profile authority fields (at minimum allowed_repositories) to be a subset of what installed pools already authorize.

General fix: during replay of PoolInstall, enforce that authority-bearing non-profile fields in the new pool config cannot expand existing authority. At minimum, require operation.config.allowed_repositories to be a subset of repositories already authorized by installed pools.

Best concrete fix in this file:

  • In actions/setup/js/work_queue_replay.cjs, inside case \u0027PoolInstall\u0027 (around current lines 1260–1264), add a subset check after validatePolicy(policy) and before mutating state.
  • Compute the set of currently authorized repositories from installedPolicy.pools (the pre-install policy snapshot), then verify every repository in operation.config.allowed_repositories is already present.
  • If not, throw queueError("policy_invalid", "...").
  • Keep existing profile checks unchanged.

This preserves functionality for legitimate installs while preventing non-admin widening of repository authority during replay. - #71495 (comment)
3. Review (actions/setup/js/work_queue_dispatch.cjs:465): Excessive agency risk: actions/setup/js/work_queue_dispatch.cjs automatically calls installWorkQueuePools on first use of a missing pool (e.g., requested.pool from an intent) before dispatch/submission proceeds. This triggers a repository-mutating publication path (actions/setup/js/work_queue_pool_install.cjs publishes a PoolInstall request via publishWorkQueueRequest) without an independent approval step tied to the specific pool/arguments. In an agent-driven dispatcher/producer workflow, an attacker-controlled or prompt-injected intent could steer the automation to add arbitrary new pool names (policy growth/state change) using the workflow's GitHub credentials.


The safest minimal fix is to remove implicit pool installation during dispatch intent handling and fail closed when the requested pool is missing.
That preserves normal dispatch behavior for existing pools while preventing this endpoint from autonomously creating new pools from untrusted/semi-trusted input.

Best single fix in this file/region:

  • In dispatchQueueIntent (around lines 464–469), replace the auto-install/synchronize block with a hard failure when prior is absent and the requested pool is not already known in projection.
  • Throw a deterministic error (e.g., work_queue_pool_not_found) so callers can route pool creation through an explicit, separately approved workflow.

No new imports or dependencies are needed. - #71495 (comment)

Push the necessary fixes, reply to each listed review thread and resolve it when addressed. Ignore feedback already answered or resolved. Use the pr-finisher skill and stop when only human review or CI remains; do not trigger CI.

Sous-chef head: 8786c98
Sous-chef work: 20462820657e0821d0628cd08150602f0e5c757d5c657ef210a2407ff67d800a f900b9b0d8694438c625f8fe428590be0b4789686ebaa38dcd98bead6cc5e965
Sous-chef state: a8588e090db7382287549a4f6443481bff04a225d603e75d76088f74181be3c5

Generated by 👨‍🍳 PR Sous Chef · pi · haiku45 · ~5 AIC · ⌖ ~10 AIC · ⊞ 1.1K · ◷
Comment /souschef to run again

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address the following outstanding work in one pass:

  1. Update this branch with the latest main using make merge-main, resolving any conflicts and preserving the intended changes....

The replay guards already reject PoolInstall repository allowlist expansion in 8786c98, in both Go and JS, and ordinary submit/dispatch requests fail closed for missing pools. make merge-main fetched main and found the branch already up to date. Focused validation passed: Go work-queue tests and 188 JS dispatch/deployment tests. The final agent gate still reports the existing custom-linter findings in pkg/workqueue/checkpoint.go; no new changes were needed for this comment.

@pelikhan
pelikhan merged commit 9121bb4 into main Oct 24, 2026
5 checks passed
@pelikhan
pelikhan deleted the copilot/fix-work-queue-issue branch October 24, 2026 10:18
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.93.4

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

Labels

None yet

5 participants