diff --git a/actions/setup/js/replace_label.cjs b/actions/setup/js/replace_label.cjs index b4cb6fb4d35..dcfbade283a 100644 --- a/actions/setup/js/replace_label.cjs +++ b/actions/setup/js/replace_label.cjs @@ -221,6 +221,17 @@ const main = createCountGatedHandler({ const updatedLabelNames = (updatedLabels || []).map((/** @param {any} l */ l) => l.name || "").filter(Boolean); + if (!updatedLabelNames.includes(labelToAdd)) { + const error = `replace_label: label_to_add ${JSON.stringify(labelToAdd)} not found in POST-setLabels response`; + core.error(error); + return { success: false, error }; + } + if (labelToRemoveIsPresent && labelToRemove !== labelToAdd && updatedLabelNames.includes(labelToRemove)) { + const error = `replace_label: label_to_remove ${JSON.stringify(labelToRemove)} still present after setLabels call`; + core.error(error); + return { success: false, error }; + } + core.info(`Successfully replaced label "${labelToRemove}" → "${labelToAdd}" on ${contextType} #${itemNumber} in ${itemRepo}`); core.info(`Updated labels: ${JSON.stringify(updatedLabelNames)}`); diff --git a/actions/setup/js/replace_label.test.cjs b/actions/setup/js/replace_label.test.cjs index dea3e0ef26a..a093bf9f302 100644 --- a/actions/setup/js/replace_label.test.cjs +++ b/actions/setup/js/replace_label.test.cjs @@ -198,6 +198,36 @@ describe("replace_label", () => { expect(result.success).toBe(false); }); + it("should reject a successful setLabels response that omits label_to_add", async () => { + mockGithub.rest.issues.setLabels = async () => ({ + data: [{ name: "bug" }], + }); + + const handler = await main({}); + const result = await handler({ label_to_remove: "in-progress", label_to_add: "done" }, {}); + + expect(result).toEqual({ + success: false, + error: 'replace_label: label_to_add "done" not found in POST-setLabels response', + }); + expect(mockCore.errors).toContain(result.error); + }); + + it("should reject a successful setLabels response that retains label_to_remove", async () => { + mockGithub.rest.issues.setLabels = async () => ({ + data: [{ name: "in-progress" }, { name: "bug" }, { name: "done" }], + }); + + const handler = await main({}); + const result = await handler({ label_to_remove: "in-progress", label_to_add: "done" }, {}); + + expect(result).toEqual({ + success: false, + error: 'replace_label: label_to_remove "in-progress" still present after setLabels call', + }); + expect(mockCore.errors).toContain(result.error); + }); + describe("allowed-transitions", () => { it("should allow a transition that is in the allowed-transitions list", async () => { const handler = await main({ diff --git a/specs/intent-attribution-compliance/README.md b/specs/intent-attribution-compliance/README.md index 12205c3e948..bc440eb4743 100644 --- a/specs/intent-attribution-compliance/README.md +++ b/specs/intent-attribution-compliance/README.md @@ -85,6 +85,17 @@ F7_SingleSourcePerRecord(a) ≜ Resolve(a) is attributed to exactly one source ``` +### Structure + +`PolicyCompiler` produces an `ExecutionPolicy` that carries the governance +decision (`autonomy`, tool restrictions, `write_scope`, required checks, +approval, auto-merge, attempts, and matched rule IDs). It does not populate +CLI outcome-report fields directly. The outcome evaluator records +`objective_value` and `objective_labels` from objective mapping and +`traced_root_url` from the resolved artifact relationship; its +`attribution_status` and `attribution_source` preserve the attribution +context that was supplied to policy compilation. + ## Behavioral Coverage Map | Predicate / Invariant | Test Function | Description | diff --git a/specs/otel-observability-spec.md b/specs/otel-observability-spec.md index 3b4f2cebed6..0b2d5566ac8 100644 --- a/specs/otel-observability-spec.md +++ b/specs/otel-observability-spec.md @@ -827,6 +827,16 @@ Job finalization SHOULD finish functional work, record result attributes and eve When an additive pipeline root span is emitted, it SHOULD end only after the workflow result and all known job results are available. +### Safeguards + +Observability is fail-closed with respect to telemetry correctness and +secrets: export failures, partial endpoint fan-out failures, and shutdown +timeouts MUST be recorded as bounded diagnostics and MUST NOT be reported as +successful delivery. They MUST NOT discard successful endpoint deliveries, +delete local mirror data, expose exporter credentials, or interrupt already +completed functional workflow work. Finalization MUST write eligible mirror +records and end known spans before its bounded exporter flush. + --- ## 17. Compliance Testing @@ -897,6 +907,8 @@ The following implementation areas are authoritative for version 0.4.0 compatibi | Frontmatter schema | `pkg/parser/schemas/main_workflow_schema.json`, `pkg/parser/schema_test.go` | | Compiler normalization and env injection | `pkg/workflow/observability_otlp.go`, `pkg/workflow/observability_otlp_test.go`, `pkg/workflow/safe_output_helpers_test.go` | | Gateway credential scoping | `pkg/workflow/mcp_renderer.go`, `pkg/workflow/mcp_setup_generator.go`, `pkg/workflow/mcp_renderer_test.go` | +| MCP access-control fixture contract | `specs/github-mcp-access-control-compliance/README.md` | +| Replace-label fixture contract | `specs/replace-label-compliance/README.md` | | Runtime setup/conclusion spans and JSONL mirror | `actions/setup/js/send_otlp_span.cjs`, `actions/setup/js/send_otlp_span.test.cjs`, `actions/setup/js/otel_contract.test.cjs` | | Header and attribute masking | `actions/setup/sh/mask_otlp_headers.sh`, `actions/setup/sh/mask_otlp_attributes.sh`, `pkg/workflow/observability_otlp_mask_script_test.go` | | Local validation target | `Makefile` target `validate-otel-contract` | @@ -949,6 +961,7 @@ context is added to outcome spans or links. - **Clarified**: A versioned mirror envelope may be added only as an additive format; `/tmp/gh-aw/otel.jsonl` remains raw OTLP/JSON lines for compatibility. - **Added**: Metric cardinality, privacy, redaction, and secret-handling guidance while preserving existing artifacts and query surfaces. - **Added**: Inlined compatibility validation requirements, optional extension tests, and the implementation map so this document is self-contained. +- **Added**: Consolidated fail-closed safeguards for export, endpoint fan-out, and shutdown handling. - **See also**: `specs/safe-output-outcome-evaluation.md` (Change Log, Version 1.0.1) for aligned outcome-taxonomy and provenance rules. ### Version 0.3.0 (Working Draft, June 15, 2026) diff --git a/specs/replace-label-compliance/README.md b/specs/replace-label-compliance/README.md index dd957b2f55e..5a0c60eda08 100644 --- a/specs/replace-label-compliance/README.md +++ b/specs/replace-label-compliance/README.md @@ -70,7 +70,7 @@ Evaluation order is modeled as: blocked check → allowlist check → gates | Edge: self-transition not implicit | `TestFormalTransitionEdge_SelfTransitionRejectedWhenNotListed` | `from == to` is not implicitly allowed unless explicitly listed | | Edge: duplicate transition entries | `TestFormalTransitionEdge_DuplicateTransitionEntriesIdempotent` | Duplicate entries in the list don't change the allow/deny decision | -Coverage parity check (2026-08-03): verified Behavioral Coverage Map entries +Coverage parity check (2026-08-05): verified Behavioral Coverage Map entries are implemented in: - `pkg/workflow/replace_label_formal_test.go` @@ -137,7 +137,6 @@ The suite runs fully in-process under Go test, reads the fixture YAML files in this directory, and does not require a JavaScript runtime to validate the formal predicates. -The RL-057/RL-058/RL-059 post-`setLabels` checks are currently formalized in -the Go suite via a formal helper stub and document a spec/implementation gap: -`actions/setup/js/replace_label.cjs` does not yet enforce these predicates on -HTTP 200 responses. +The RL-057/RL-058/RL-059 post-`setLabels` checks are enforced by +`actions/setup/js/replace_label.cjs` and covered by +`actions/setup/js/replace_label.test.cjs`.