# Review record — `apollo-deployment-slice2-spec.md`

## Round 1 — draft 1 (`2e2a9a9`), 2026-09-03

Two independent, read-only reviewers on the owner's word, after the six decisions D-S2-1…6 were
ruled. Reviewer P: protocol fidelity to Slice 1 (contracts, handler table, capability preflight,
compiler, sanitizer, dual build, evidence). Reviewer O: operations and safety against the live
host (section 2's facts verified read-only against IIS, ACLs, SCM, http.sys, win-acme; adapter
semantics; rollback, journal, resume; safety gates). Both verdicts: **REVISE**.

Adjudicated by the author; "Applied" = in revision 2. The row numbers keep each reviewer's own
numbering (P#, O#).

| # | Sev | Finding (short) | Adjudication → revision 2 |
|---|---|---|---|
| P1 | Critical | No Slice 2 handler table: the V2 targets change argument schemas of a dozen S1 keys; the S1 registry oracle cannot stay "untouched and passing" | Applied: a second canonical table `CreateProductionV2()` with the schema-change table and seven new precondition keys (36 keys), `registry.v2.json` typed independently before B2; S1's table and oracle untouched (§5.1) |
| P2 | Critical | No Slice 2 phase-allocation table; no RED order oracle possible | Applied: §5.2, three hosts, ten checks, preconditions as named operations, derived compensation |
| P3 | High | Adapters in Core break the S1 byte scan and forbidden-name rules | Applied: two new assemblies (`Execution` dual-TFM, `Adapters.Windows` net48-only); Core and its scan untouched; `forbidden.v2.json` with the eight lifted member names (§0.2) |
| P4 | High | Toolchain unproven for adapters on both TFMs | Applied: spike done the same night (net48 + `Microsoft.Web.Administration` 7.0.0.0 GAC + `System.ServiceProcess`, built offline, loaded in PS 5.1, read 12 sites and the service); Adapters.Windows is net48-only and exempt from the transferability rule (§0.2) |
| P5 | High | How the S1 fail-closed capability gate opens under `-Execute` is unstated | Applied: `CapabilityPreflight.Check(table, invoker, mode)`; Plan keeps S1 rules; Execute requires exactly the three capabilities; mode only from the bound switch (§5.1) |
| P6 | High | "V2 beside V1" not implementable on the V1-typed pipeline; per-mode outputs undefined | Applied: a V2 pipeline (`IDeploymentPhasesV2`, `ISibylaCommandInvokerV2`, `DeploymentOrchestratorV2`); without `-Execute` cmdlets emit V1 exactly as today (§3, D-S2-6 truth table) |
| P7 | Medium | `S2-CONTRACT-VERSION` unreachable from the public surface | Applied: V1→V2 handoff fails `SIBYLA-S2-CONTRACT-IDENTITY`; version code internal (§3) |
| P8 | High | The failed intent's own compensation would be `Skipped` by the `DependsOn` rule | Applied: §5.3 — compensation of a `Failed` forward runs from the snapshot with the failed receipt bound; `RolledBack` only when every compensation and readback succeeded |
| P9 | High | Resume cannot match recompiled intent ids; no stable plan hash | Applied: plan digest without ids; matching by `(phase, operation, ordinal)`; `TransactionId` value object and `INPUT-TRANSACTION-ID` (§6) |
| P10 | High | Cross-process mutex between separate phase invocations; double release | Applied: execution only through `Invoke-SibylaDeployment -Execute`; phase cmdlets refuse `-Execute`; `Mutex.Release` idempotent and journal-checked; abandoned mutex rule (§0.1, §5.1) |
| P11 | High | Value objects make disposable-target proof (C2) impossible | Applied: probe names accepted only through the `DeploymentRuntime` internal override, production sources grep-proven never to reference them; C2 targets, bindings, service, mutex and cleanup named (§9) |
| P12 | High | `PRECONDITION-ROLE` needs a database client no adapter has | Applied (with O1): no database adapter; `GET /health/schema` on the running API, checked by `Http.GetNoRedirect` (§5.5) |
| P13 | High | `Test -Execute` would run `Process.Start`/`Mutex.Release` on production | Applied: `-ChecksOnly` on the one-shot; `Test -Execute` refused (§4) |
| P14 | Medium | Switch parity, `-Force` semantics, Confirm on phase cmdlets, WhatIf×Execute table | Applied: parameter table per cmdlet, `-Force` = `-Confirm:$false`, non-interactive fail-closed, truth table (§1 D-S2-6, §4) |
| P15 | Medium | `SafeScmName` change contradicts S1 validators/entrypoints oracles and code names | Applied: the S1 oracle amendment is enumerated in A2; one code per value object (`SIBYLA-S2-INPUT-SCM-NAME`) (§9) |
| P16 | Medium | New parameters/codes fail the S1 closed sets and render pattern | Applied: `KnownParameterNames`/`KnownOperationNames` V2 sets, `sanitizer.v2.json` `^SIBYLA-S[12]-` (§4, §8) |
| P17 | Medium | Secret-pattern rule as written makes every journal line a violation; no redaction definition | Applied: path patterns on error surfaces only; `Redaction.Render` grammar; adapter exceptions discarded whole (§8) |
| P18 | Medium | SCM `Environment` needs a value object | Applied: `EnvironmentAssignment` with the closed name set (§4) |
| P19 | Medium | Nothing produces the artifact under `-Execute` | Applied: Publish verifies a pre-published staging tree, never builds (§5.4) |
| P20 | Medium | Preconditions as value objects would violate `[R2-N8]` | Applied: read operations plus pure predicates (§5.1 new keys) |
| P21 | Low | Pool identity SID resolution before the pool exists | Applied: pools pre-exist (ownership precondition); C2 harness creates the probe pools first (§5.1, §9) |
| P22 | Low | API/docs health need `HttpsUri` inputs | Applied: `ApiBaseUri`, `DocsBaseUri` (§4) |
| P23 | Low | ACL targets per parameter unspecified | Applied: section 2 target list and `Acl.*` schema (§5.1) |
| P24 | Low | Journal writer is a ninth I/O component | Applied: lives in `Execution/Journal/`, the only place allowed `System.IO` there (§0.2) |
| P25 | Low | `Microsoft.Web.Administration` resolution for PS 5.1 and the manifest | Applied: GAC reference identity and manifest edit stated (§0.2) |
| O1 | Critical | Role/migration precondition needs a credential the module may not hold | Applied: `/health/schema` (§5.5); `pg_stat_activity` items struck from the module's checks (§2) |
| O2 | High | Live ACLs broader than stated: inherited `Users` CreateFiles on release, docs and data folders; config-file injection possible | Applied in the spec (§2 ACL shape, readbacks for absent `appsettings.*.json` and `docs\web.config`) **and on the host the same night** by `provision-production.ps1` (run record) |
| O3 | High | Standalone phase `-Execute` leaves the host half-deployed; `-Force`/Confirm semantics | Applied (with P10, P14) |
| O4 | High | Journal `Started` not durable before the adapter call; unknown intents never compensated | Applied: fsync before and after; `Started` without terminal → readback-compare then compensate or report (§6) |
| O5 | High | Resume overwrites newer legitimate state | Applied: live-vs-journaled post-state comparison, `JOURNAL-MISMATCH`, `-Force` cannot override (§6) |
| O6 | High | Fixed thumbprint downgrades win-acme's renewal; concurrent renew | Applied: optional thumbprints, newest valid SAN cert by default, no-op rule, `PRECONDITION-RENEWAL` (§2) |
| O7 | High | SCM `Environment` names-only snapshot cannot roll back; secrets pattern in the legacy key | Applied: name+value when every value is a path, else `PRECONDITION-SCM-ENV`; adapter opens only the mandated key (§5.1) |
| O8 | Medium | Docs anonymous auth and default document are effective-only, not in the location section | Applied: readback = effective merged value + absence from `web.config` (§2) |
| O9 | Medium | `CURRENT.txt`/`PREVIOUS.txt` exist only for `api` | Applied: "previous" derived from live state; files tolerated absent (§2) |
| O10 | Medium | ACL objects: host folders, not release folders | Applied: target list; no per-release ACE (§2, §5.1) |
| O11 | Medium | `PublishTree` refusal makes a retry under the same release impossible | Applied: `.partial` + rename, idempotent by tree hash, compensation renames to `.failed-<txid>` (§5.1) |
| O12 | Medium | Pool stop/recycle semantics wrong for three sites; docs recycled needlessly | Applied: pool `Stop(Deployment)` = snapshot no-op; recycle after each site's `Iis.Apply`; docs never recycled; scoped safety stops (§5.1, §5.2) |
| O13 | Medium | `Running` right after start is not evidence; no HTTP retry window | Applied: pid stable ≥ 20 s + executable path; HTTP retries in a 90 s budget (§5.1) |
| O14 | Medium | Abandoned mutex; scripts and win-acme take no mutex | Applied: abandoned = acquired with warning and fresh snapshots; non-exclusion stated (§5.1, §6, §10) |
| O15 | Medium | Double compensation on Resume; no idempotency requirement | Applied: idempotent rollbacks, `CompensationFailed` journaled (§5.3) |
| O16 | Medium | `Iis.Rollback` must not be a whole-file restore | Applied: per-site element rewrite; diff test (§5.1) |
| O17 | Medium | Ownership precondition vs the docs path exception | Applied: docs ownership rule (§2) |
| O18 | Medium | `Acl.Apply` on the secrets root every run is a lock-out risk | Applied: secrets root and leaves readback-only (D-S2-1) |
| O19 | Medium | Two repoints (`FileSystem.Rollback` vs `Iis/Scm.Rollback`) | Applied: text files only vs authoritative repoint (D-S2-2) |
| O20 | Medium | No disk-space precondition | Applied: `PRECONDITION-DISK` (§5.1) |
| O21 | Low | Pool SID before pool exists (C2) | Applied with P21 |
| O22 | Low | Web `/` fixed at 200 | Applied: `RootExpectedStatus` input (§4) |
| O23 | Low | Hairpin dependency; `Location` query noise | Applied: `HealthHostOverride`; query redaction (§4, §5.1) |
| O24 | Low | Docs working copy: `.git` served, `docs\web.config` not ignored | Applied: `/.git/HEAD` 404 check; `.gitignore` entry added the same night |
| O25 | Low | Worker stop timeout and `CLAUDE_CONFIG_DIR` never touched | Applied (§2, §10) |
| O26 | Low | Start type must never be written `Manual`/`Disabled` | Applied: fixed value (D-S2-3) |

**Author's verdict on round 1: REVISE accepted in full; revision 2 written the same night.**

## Round 2 — revision 2 (`fe8442c`), 2026-09-03

Same two reviewers, read-only. Reviewer P verified P1–P25 item by item (12 Yes, 11 Partly, 1 No
— P11, the probe seam) and raised N1–N17; reviewer O verified O1–O26 (23 Yes, 3 Partly — O4, O6,
O12) and the hardened host (every ACL target matches; four folders carried a stray explicit
`Administrators` ACE), and raised N1–N17. Both verdicts: **REVISE**. Adjudicated and applied in
revision 3 the same night.

| # | Sev | Finding (short) | Adjudication → revision 3 |
|---|---|---|---|
| P-N1 | High | "Core untouched" false: V2 types, tables, codes, names would all live in Core and break the S1 scan | Applied: V2 everything lives in `Execution`; Core changes in exactly one place (`SafeScmName`), enumerated (§0.2) |
| P-N2 | High | Cmdlets' closed assembly-reference list breaks; V2 composition root unnamed | Applied: `forbidden.json` amendment enumerated; `DeploymentRuntimeV2` in Execution; Adapters.Windows referenced from Cmdlets only (§0.2) |
| P-N3 | High | Health counts contradict each other; S1 `health.json` has six names | Applied: one table of eleven named checks with keys, targets and expectations; eight HTTP intents (§7, `health.v2.json`) |
| P-N4 | High | `ExpectedStatus` cannot express 404; no content-type/Location argument or evaluation seam | Applied: `ExpectedStatus` `200|302|404`; `HttpExpectation` argument (status, content type, `LocationAssertions`); pure predicates over `HttpResponseEvidenceV2` (§5.1, §7) |
| P-N5 / O-N2 | Medium | `Process.Recycle` used but not a key; docs recycled on rollback | Applied: key 30 `Process.Recycle` (mutation, no compensation, readback = Started + new pid), web/api only; docs never recycled (§5.1, §5.2) |
| P-N6 | Medium | Codes without producers or duplicated | Applied: one code ↔ one producer table; S1 codes reused where the value object is S1's (§8) |
| P-N7 | Medium | Precondition failure semantics absent | Applied: `PreconditionReceiptV2.Satisfied = false` → `Failed` with the key's code (§5.1) |
| P-N8 | Medium | Cross-phase compensation holding and not-run dispositions unstated | Applied: held lists, `Skipped(NotReached|NotNeeded)`, plan-sized `OperationResults` (§5.3) |
| P-N9 | Medium | V2 planning types missing | Applied: enumerated (§3) |
| P-N10 | Medium | `-Execute -WhatIf` pipeline undefined | Applied: V2 plan-only invoker, the E2 preview (§5.6, D-S2-6) |
| P-N11 / O-N11 | Medium/Low | Parameter table wrong: one-shot lacks the host parameters, phase cmdlets gain dead ones, `-ChecksOnly` missing, non-literal default | Applied: corrected table; `ChecksOnly` rules (no Confirm, reads-only invoker, refused with `-Resume`); `JournalRoot` mandatory literal (§4) |
| P-N12 | Medium | Two exact oracles for one parameter list | Applied: `entrypoints.json` amended in full, no v2 file (§4, §9) |
| P-N13 / P11 / O-N7 / O-N8 | Medium | C2 harness infeasible and unsafe: internal override unreachable from powershell.exe, const mutex, probes not elevated, worker exe with a probe secrets file, no cleanup of http.sys/thumbprint, hosts-file temptation | Applied: the **probe profile** selected only by `SIBYLA_DEPLOYMENT_PROFILE=Probe`, grep-guarded; profile-provided names and mutex; value objects refuse cross-profile targets; elevated probe runner; stub host, dedicated account, port 8443, `HealthHostOverride 127.0.0.1`, pre-clean, `finally` clean, backup and diff assertions (§9) |
| P-N14 / O-N14 | Medium/Low | `/health/schema`: API spec C-row, sequencing, static list, disclosure | Applied: C-row and test in A2; `requiredMigrations` in `release.json` written by `publish-release.ps1`; runs in Install (refusal) and Health; first-run rule; disclosure accepted (§5.5) |
| P-N15 / O-N3 | Low/Medium | Preconditions run after Install mutated | Applied: all preconditions in Install right after the mutex; `Renewal` repeated at Configure start (§5.2) |
| P-N16 | Low | Single wire version in `ContractIdentity` | Applied: `ContractVersionsV2`, per-type expected version (§3) |
| P-N17 | Low | Redaction drops `&`/`?`/`%`; `Location` keys lossy | Applied: kept keys rendered as separate `Observed` fields (§7) |
| P1/P2 partial | — | `×N`/`×M` not derivable; precondition keys without adapter/kind/result; `PRECONDITION-ARTIFACT` producer | Applied: ACL target table (N = 14, M = 9), full 38-key table with adapter/kind/result/arguments, `Precondition.Artifact` (§2, §5.1) |
| P5 partial | — | Contradictory codes for an executing invoker under Plan; invoker type | Applied: `CapabilityPreflightV2.Check(tableV2, invokerV2, mode)` with `EXECUTION-REFUSED`; S1 `Check` untouched (§5.1) |
| P8 partial | — | Compensations of successful phases would run | Applied with P-N8 |
| P9 partial | — | Digest ref rendering | Applied: `(sourceOperationName, sourceOrdinal)` (§6) |
| P13 partial | — | `-ChecksOnly` plan and rules | Applied (§4, §5.2) |
| P14/P15/P16/P20/P23 partial | — | `-Force`/non-interactive code, `SafeScmName` code, S1 oracle rows, category/state values, predicate evaluation, ACL targets | Applied: `EXECUTION-UNCONFIRMED`; S1 code kept; `sanitizer.v2.json` categories/states; §5.1 predicates; §2 table |
| P18/P19/P22 | — | `INPUT-ENVIRONMENT` unreachable; release id derivation; duplicate `INPUT-URI` | Applied: internal; `release.json` under `StagingRoot`, no `Release` parameter; S1 code reused (§4, §5.4) |
| O-N1 | High | Pools stopped by the safety stop are never started again after a rollback | Applied: no safety stop for pools; `Process.Recycle` after each `Iis.Rollback(web/api)`; B2 test "every pool whose Apply ran is Started after any rollback" (§5.2, §5.3) |
| O-N4 | Medium | `Started`-without-terminal comparison against a post-state that was never written | Applied: snapshot → `Skipped`; intended post-state → compensate; else mismatch (§6) |
| O-N5 | Medium | N/M not enumerated; `dp-keys` script grant strips the worker | Applied: target table, `dp-keys` readback-only {Web, Worker}; `provision-production.ps1` fixed the same night (§2) |
| O-N6 | Medium | Mid-run renewal breaks `Tls.Readback`/`Rollback` | Applied: renewal-safe readback and rollback; `wacs.exe` process check; "newest" defined (§2) |
| O-N9 | Low | Stray explicit `Administrators` ACEs; record says "no ACE" | Applied: tolerated rule stated; the four stray ACEs removed the same night (§2, run record 7c) |
| O-N10 | Low | `.partial`/`.failed` beside releases; scripts sort by name | Applied: `.staging\` sub-folder; release regex in the scripts (§2, §5.1) |
| O-N12 | Low | `Operator` principal source | Applied: the running principal, journaled (§2) |
| O-N13 | Low | Compensation after `JOURNAL-IO` | Applied: best-effort, unjournaled, `RollbackIncomplete` (§5.3) |
| O-N15 | Low | "staging" ambiguity | Applied: "artifact trees" (§5.2) |
| O-N16 | Low | Worker down for the whole copy | Applied: service stop after the copies and ACLs (§5.2) |
| O-N17 | Low | Volume roots keep the default ACL | Note only; not targets |

**Author's verdict on round 2: REVISE accepted in full; revision 3 written the same night.**

## Round 3 — revision 3 (`ab5e593`), 2026-09-03

Same two reviewers. P: 18 of the open items Yes, 8 Partly, new N1–N16. O: host exactly as §2
(eleven sub-folders with tolerated `Administrators` ACEs), 16 Yes, 2 Partly, new N1–N10. Both
verdicts: **REVISE**. Adjudicated and applied in revision 4 the same night.

| # | Sev | Finding (short) | Adjudication → revision 4 |
|---|---|---|---|
| P-N1 / P-N2 | High | The probe profile's environment lookup trips the S1 scan (`System.Environment`, `Environment.` source ban); `SafeScmName` and the mutex are Core consts | Applied: **no profile, no environment variable**. C2 probes exercise the adapters at adapter level through Execution's interfaces with harness-built typed arguments; Core changes in exactly two places, both enumerated (`SafeScmName` accepts the production name or `ZZ.Sibyla.Probe.Service`); the production mutex is used by the probes on purpose (§0.2, §5.1, §9) |
| P-N3 | Medium | No V2 sanitizer type; S1 codes the V2 path needs not in the allowlist; `parameters.json` amendment asserts Core's set | Applied: `DeploymentFailureV2`/`ErrorBoundaryV2` in Execution, `FailureCodesV2` = S2 table + full S1 allowlist, category column, no `parameters.json` amendment (§0.2, §8) |
| P-N4 | Medium | Registry oracle not typeable: grouped rows, Kind vocabulary, `HostKind` domain, mutex name | Applied: one row per key (38) with exact ordered argument names and families, Core's `MutationKind`, `HostKind ∈ {Web, Api, Docs, Worker}`, the descriptor stated (§5.1, §3) |
| P-N5 | Medium | Eighth HTTP intent feeds no check; schema evidence is a hash | Applied: seven `Http.GetNoRedirect` + `Precondition.Schema` with its own GET; evidence carries a ≤ 4 KiB redacted body for that key (§5.1, §7) |
| P-N6 | Medium | `Skipped(AlreadyAtSnapshot)` breaks dependencies; a refusal after the mutex ends `RollbackIncomplete`; failed-run surface unstated | Applied: no-ops are `Succeeded(NoChange)`; `Skipped` reserved for not-run; `RolledBack` iff no `CompensationFailed`; the failed-run surface and codes `ROLLED-BACK`/`ROLLBACK-INCOMPLETE` (§0.3, §3, §5.3, §8) |
| P-N7 | Medium | ChecksOnly plan not compilable; preflight mode contradiction | Applied: a ChecksOnly plan row with its own snapshots and live-bound readbacks; preflight `ChecksOnly` requires `!CanExecuteMutations`; journaled (§5.1, §5.2) |
| P-N8 | Medium | `forbidden.v2.json` incomplete | Applied: allowed namespaces per adapter folder incl. `System.IO`, `System.Diagnostics.Process`, `System.Management`, the Task Scheduler COM interface (§0.2) |
| P-N9 | Low | Fixed entries unlisted | Applied: five fixed entries named with role `Fixed`, `CompensatesIntentId = null` (§5.2) |
| P-N10 | Low | Which entry carries `Compensated` | Applied: journal lines keyed on the forward, dispositions reduced to five (§0.3, §6) |
| P-N11 | Low | S1 mandatory parameters under `-Execute`; `SecretsRoot` vs phase cmdlets | Applied: per-parameter rule; `INPUT-SECRETS-PATH` (§4, D-S2-1) |
| P-N12 / O-N3 | Low/Medium | First-run 404 exemption undecidable | Applied: struck; 404 fails the precondition (§5.5) |
| P-N13 | Low | `ADAPTER-FAILED` second producer; `INVOKER-DEPENDENCY` without trigger | Applied: `REDACTION` code; `INVOKER-DEPENDENCY` dropped (§8) |
| P-N14 | Low | Health literals vs S1 inputs | Applied: checks 1–5 on `BaseUri`, `ChallengePath`, `IdentityAuthority`, `CallbackUri` (§7) |
| P-N15 | Low | Profile guard coverage | Moot: no profile |
| P-N16 | Low | Gate commit without git | Applied: `.git\HEAD` + ref file (§2) |
| P1/P11/P13/P16/P-N1…N3/N11 partials | — | as above | Applied |
| O-N1 | **High** | Unconditional service safety stop leaves the worker stopped after a precondition refusal | Applied: Install never touches a process; the service is stopped in Configure right before `Scm.Apply`; safety stops are conditional on the protected intent having `Started`; B2 test "service in its snapshot state after any rollback" (§5.2) |
| O-N2 | Medium | `Tls.Rollback` before `Iis.Rollback`; binding rewrite drops the http.sys entry | Applied: `Iis.Rollback` attribute by attribute, never rewriting a binding equal to the snapshot; `Tls.Rollback` last, Configure's list ends with `Tls.Readback` ×3 (§5.1, §5.2) |
| O-N4 | Medium | Resume order; non-mutating `Started` lines; `.partial` | Applied: walks the compiled lists in plan order; `Read` intents `Skipped(NotReached)`; `.partial` snapshot-equal and renamed (§6) |
| O-N5 | Low | Sub-folders still carry `Administrators` ACEs; strip non-recursive | Applied: recursive strip in the script and on the host; §2 tolerance rule kept; run record corrected |
| O-N6 | Low | win-acme window wrong | Applied: 09:00 UTC + up to 4 h, 2 h limit; E2 outside the window (§2, §10) |
| O-N7 | Low | Probe account and TLS chain | Applied: `LocalService`, no account; pinned probe thumbprint under the harness (§9) |
| O-N8 | Low | ChecksOnly sources | Applied: live paths and the active `MANIFEST.json` (`requiredMigrations` copied by `deploy-release.ps1`) (§2, §5.2) |
| O-N9 | Low | Worker down through Configure | Applied: stopped right before `Scm.Apply` (§5.2) |
| O-N10 | Low | `rollback-release.ps1` guard and wait | Applied in the script the same night |

**Author's verdict on round 3: REVISE accepted in full; revision 4 written the same night.**

## Round 4 — revision 4 (`4fc77c2`), 2026-09-03

Same two reviewers. O: host exact (0 explicit ACEs below every target), 9 of round 3's items Yes,
1 Partly, new N1–N9 (2 Medium). P: 11 Yes, 5 Partly, new F1–F18 (1 High, 7 Medium). Both verdicts:
**REVISE**. Adjudicated and applied in revision 5 the same night.

| # | Sev | Finding (short) | Adjudication → revision 5 |
|---|---|---|---|
| P-F1 | High | The C2 grep oracle forbids the literal the Core change puts into Core | Applied: Core changed in exactly one place (the const rename); the probe name lives in `Execution/Inputs/ServiceNameV2.cs`, the grep's single exemption; entry points can never name it (§0.2, §9) |
| P-F2 | Medium | Second Core change (const default) unenumerated; `entrypoints.json` row | Applied: the const rename **is** the one change; row keeps `onlyValidValue`, default updated (§0.2) |
| P-F3 | Medium | Health's safety stop both derived and fixed | Applied: derived compensation of `Process.Start`; fixed entries = four (§5.1 row 12, §5.2) |
| P-F4 | Medium | No failure semantics for readbacks, health expectations, busy mutex | Applied: typed refusals (`Matches`, `Acquired`, `Satisfied`), codes `READBACK-*`, `HEALTH-1…11`, `MUTEX-BUSY` via `Acquired`, categories (§0.3, §5.3, §7, §8) |
| P-F5 | Medium | `RequiresElevation` missing | Applied: elevation rule per adapter (§5.1) |
| P-F6 | Medium | Release-derived values vs whole-plan digest, resume, `-WhatIf` | Applied: release-agnostic plans (`Release: ref<PublishedArtifactV2>`), per-phase compile and digests, resume rebuilding contracts from receipts (§5.7, §6) |
| P-F7 | Medium | `forbidden.v2.json` incomplete; JSON; `sourceBans` scope | Applied: per-folder namespaces incl. MWA in Process, `System.Net.Sockets` in Http, registry-only Scm; hand-rolled JSON under `Execution/Json/`; `sourceBans`/`langVersion` for Execution; `System.Environment` banned (§0.2) |
| P-F8 | Medium | ChecksOnly readback source | Applied: `LiveSnapshot` and `LiveManifest` arguments; empty `Receipts` legal (§5.1 rows 14, 38; §5.2) |
| P-F9…F18 | Low | Order rule wording, exemptions, `BackupRoot`, body ownership, `client_id`, truth-table rows, `-NoRollbackOnHealth` mutex, `Failed` outcome state, `ExpectedModule`, family grammar | Applied (§5.2 intro, §4, §5.1 row 27/38, §7 check 3, D-S2-6, §5.3, §3, row 14, §5.1 grammar) |
| O-N1 | Medium | Fixed recycles unguarded; recycle of a Stopped pool fails | Applied: recycles conditional on the site's `Iis.Apply` having `Started`; `Process.Recycle` starts a Stopped pool (§5.1 row 30, §5.2) |
| O-N2 | Medium | Binding equality includes the certificate | Applied: `Iis.*` never writes bindings; equality = (protocol, bindingInformation, sslFlags) (§2, §5.1) |
| O-N3 | Low | Resume mismatches after a renewal; pending service transitions | Applied: resume defers to the adapter's rollback rule; waits out pending transitions (§6) |
| O-N4 | Low | SCM failure-action restart races the rollback | Applied: `Process.Rollback(Service)` compares `ExecutablePath` (§5.1) |
| O-N5 | Low | Overlapped recycle: two w3wp | Applied: the newest w3wp (§5.1, §7) |
| O-N6 | Low | Compensation of a `NoChange` PublishTree | Applied: `NoChange` (§5.1) |
| O-N7 | Low | Recursive ACL readback over production data | Applied: recursion for install trees and `DocsPath` only (§2) |
| O-N8 | Low | Harness backup/restore hazard | Applied: restore manual only, backup removed in `finally` (§9) |
| O-N9 | Low | Which manifest; receipt post-state | Applied: the api host's `MANIFEST.json`; receipts carry a post-state digest (§0.3, §5.1 row 38) |

**Author's verdict on round 4: REVISE accepted in full; revision 5 written the same night.**

## Round 5 — revision 5 (`60c6208`), 2026-09-03

Same two reviewers; no High on either side. P: 13 of round 4's items Yes, 5 Partly, new N1–N5
(Medium) and L1–L12. O: host exact and healthy, all nine round-4 items Yes, the crashing-release
scenario walked end to end and correct, one new Medium and nine Lows. Both verdicts: **REVISE**.
Adjudicated and applied in revision 6 the same night.

| # | Sev | Finding (short) | Adjudication → revision 6 |
|---|---|---|---|
| P-N1 | Medium | Checks 6/7/9 have two producers (`READBACK-PROCESS`/`PRECONDITION-SCHEMA` vs `HEALTH-n`) | Applied: in the Health and ChecksOnly plans the check's code overrides the key's (§8) |
| P-N2 | Medium | Fixed entries' disposition on success and under `-NoRollbackOnHealth` | Applied: only Install's `Mutex.Release` always runs; other fixed entries run only for a rollback and when their `GuardIntentId` reached `Started`, else `Skipped(NotNeeded)`; B2 tests (§5.3) |
| P-N3 | Medium | Readback `Receipts` typing regression; union `LiveSnapshot`; no snapshot for the rollback readback | Applied: `receipts` family (S1's `ReceiptRefSet`) in the six readback rows; `LiveIis`/`LiveScm`; `Snapshot` xor `Expected` in row 14 (§5.1) |
| P-N4 | Medium | Cross-phase plan carriage unstated | Applied: every V2 contract carries `Plan` and `OperationResults`; `PlanLineageValidatorV2` as S1; `GuardIntentId` on `PlannedOperationV2` (§3) |
| P-N5 | Medium | `Adapters/Acl/` needs `System.IO` for DACL writes and enumeration | Applied (§0.2) |
| P-L1…L12 | Low | Row lists, rename side edits and case sentinels, `ClientId`, dead `Pool(Api)` module, ChecksOnly snapshots, grammar (`HttpExpectation`, `AclGrant`, enum names, row 33 adapter), `GuardIntentId`, truth-table rows, digest source phase and receipt fields, resume mutex bracket, grep scope, budget exhaustion / internal code / read-only invoker | Applied (§0.2, §3, §4, §5.1, §5.2, §6, §7, §8, D-S2-6) |
| O5-1 | **Medium** | `Process.Readback(Pool web)` before any request: an in-process site loads its dll on the first request, so a healthy release would be rolled back on a quiet host | Applied: Http 1 before the web pool readback, Http 3 before the new api pool readback (check 12), in Health and ChecksOnly (§5.2, §7) |
| O5-2 | Low | Resume of a journal with every forward succeeded would compensate a completed deployment | Applied: trailer `Executed`, nothing compensated (§6) |
| O5-3 | Low | The harness diff could print the legacy pools' connection string | Applied: hash and element-path comparison, values never printed (§9); **owner note below** |
| O5-4 | Low | `.failed` rename can hit a sharing violation while a w3wp drains | Applied: retry within `shutdownTimeLimit` (§5.1) |
| O5-5 | Low | Scm registry writes not atomic; resume comparison | Applied: per-value comparison; C2 proves `ImagePath` honoured without reboot (§5.1) |
| O5-6 | Low | Text files written in place | Applied: temp + atomic rename (§5.1) |
| O5-7 | Low | Post-state digest undefined | Applied: per-adapter definition, pid excluded (§3) |
| O5-8 | Low | A deliberately stopped pool would be started | Applied: `Precondition.Ownership` requires web/api pools `Started` (§5.1 row 33) |
| O5-9 | Low | `Pool(Api)` never read back | Applied: check 12 `ApiProcessServesActivatedRelease` (§7) |
| O5-10 | Low | `Operator` ACE on `DocsPath` follows the running principal | Applied: E2 runs as the working copy's owner (§9) |

**Owner note (pre-existing host fact found read-only in round 5, not caused by this work):**
the legacy application pools' `environmentVariables` in `applicationHost.config` carry the
`gott_sibyla` connection string with its password, and the same appears in the IIS configuration
history under `C:\inetpub\history`. It belongs to Sibyla Legacy and to the owner's retirement
step (Phase D); nothing in Slice 2 reads or prints it.

**Author's verdict on round 5: REVISE accepted in full; revision 6 written the same night.**

## Round 6 — revision 6 (`fbd1a01`), 2026-09-03

**Reviewer O: ACCEPT** — host exact and healthy (0 explicit ACEs below every target, preload off
on all pools confirming the round-5 premise), all ten round-5 items applied with executable
semantics, the §5.2 walk clean; six Lows as notes (N1–N6). **Reviewer P: REVISE** — round-5
items resolved except two enumeration gaps; two one-line Mediums and ten Lows. Adjudicated and
applied in revision 7 the same night.

| # | Sev | Finding (short) | Adjudication → revision 7 |
|---|---|---|---|
| P6-1 | Medium | Process rows carry no pool/service name argument | Applied: `ProcessTargetV2 = (Workload, SafeIisName \| ServiceNameV2)` in rows 10–14, 30, 33 (§5.1) |
| P6-2 | Medium | No outcome for a Health failure under `-NoRollbackOnHealth` | Applied: `Failed`, `HEALTH-n` thrown, state `Started`, trailer `NoRollbackOnHealth` (§5.3) |
| P6-3 | Low | S1's `ReceiptRefSet` cannot be empty and is V1-typed | Applied: `ReceiptRefSetV2`, empty allowed where stated (§5.1) |
| P6-4 / O6-N1 | Low | Completed-deployment predicate vs the `None` mutex intents | Applied: every `Read`/`Write` forward `Succeeded`, mutex intents excluded (§6) |
| P6-5 | Low | Readback of the derived safety stop | Applied: `Expected = Stopped` (§5.3) |
| P6-6 | Low | Which `HEALTH-n` when one intent feeds several checks | Applied: the lowest-numbered unmet check (§5.3) |
| P6-7 | Low | Sanitizer pattern has no digits | Applied: `^SIBYLA-S[12]-[A-Z0-9-]+$` (§8) |
| P6-8 | Low | No invoker timeout | Applied: 300 s per intent (§5.1) |
| P6-9 | Low | `entrypoints.json` sentinel and `validators.json` variants | Applied: enumerated (§0.2) |
| P6-10 | Low | Row 11 enum name, unused `int`, row 38 `LiveManifest` position | Applied (§5.1) |
| P6-11 | Low | Truth-table rows for `-Execute -Resume` and ChecksOnly switch combinations | Applied (D-S2-6) |
| P6-12 | Low | `StagingRoot` kind; unused docs pool snapshot | Applied: kind added; `Process.GetSnapshot` ×3 (§5.1, §5.2) |
| O6-N2 | Low | Resume of a journal with a trailer | Applied: three cases, B2 test for `-Resume` after `-NoRollbackOnHealth` (§6) |
| O6-N3 | Low | "owns the working copy" ambiguous | Applied: the holder of the `Operator` ACE, compared on readback (§9) |
| O6-N4 | Low | Redaction could alter receipt fields resume relies on | Applied: lossless-or-fail for receipt fields (§8) |
| O6-N5 | Low | Shadow copy / out-of-process would fail as a health rollback | Applied: `FileSystem.Readback` asserts in-process and no shadow copy (§2) |
| O6-N6 | Low | Rename retry budget tied to a pool value for the worker | Applied: host drain time per workload (§5.1) |

**Author's verdict on round 6: O's ACCEPT stands; P's REVISE accepted in full; revision 7 written
the same night.**

## Round 7 — revision 7 (`f569309`), 2026-09-03

**Reviewer P: ACCEPT** — all twelve round-6 items resolved with testable semantics, every
invariant re-checked (38 rows, four fixed entries, HEALTH-1…12, the single Core change against
every occurrence of the old name in the tree), ten Lows as notes (P7-1…10). **Reviewer O (delta
check): REVISE** — five of its six Lows applied, one Partly; one High regression introduced by
revision 7, two Mediums, four Lows. Adjudicated and applied in revision 8 the same night.

| # | Sev | Finding (short) | Adjudication → revision 8 |
|---|---|---|---|
| O7-F1 | **High** | The lossless-or-fail rule, applied against a render set without `_`/`*`, would fail `Scm.GetSnapshot`/`Iis.GetSnapshot` (and checks 3–4) on every production run — a deterministic Install rollback | Applied: receipt fields are rendered losslessly by `\uXXXX` escaping, the 512 cap is per field, and a B2 test renders every section-2 literal through the receipt path (§8) |
| O7-F2 | Medium | The 300 s timeout said nothing about a still-running `Write` adapter; compensation could run concurrently with its own forward | Applied: cancellation deadline; adapters observe the token; no compensation starts while a `Write` call executes; bounded by the drain time, else `CompensationFailed` (§5.1) |
| O7-F3 | Medium | On a resume after `-NoRollbackOnHealth` the conditional fixed entries' `Skipped(NotNeeded)` lines would leave the pools serving the abandoned release | Applied: resume re-evaluates them from the guard's journaled `Started`; B2 asserts both recycles and the served release (§6) |
| O7-F4 / P7-7 | Low | Plain `Failed` trailer | Applied: refuse (§6) |
| O7-F5 / P7-6 | Low | "died at or after Schema" wording | Applied: after its `Succeeded` line; dying during it rolls back (§6) |
| O7-F6 | Low | `Failed` after a partial mutation without a compensation line | Applied: treated like `Started` without terminal (§6) |
| O7-F7 / P7-1 | Low | Pool names unguarded; pool→site resolution | Applied: `Sibyla.Legacy.` ban and distinctness for pool names; Ownership resolves the one site using the pool and requires it to be the current pool; mismatch code (§4, §5.1 row 33) |
| P7-2, P7-3 | Low | `AclTargetKind` members; `HttpExpectation` fields | Applied (§5.1 grammar) |
| P7-4 | Low | Docs pool in the "every pool" rules | Applied (§0.4, §5.2) |
| P7-5 | Low | Negative sentinel attribution | Applied (§0.2) |
| P7-8 | Low | `-Execute -Resume -NoRollbackOnHealth` | Applied: ignored with a warning (D-S2-6) |
| P7-9 | Low | `Failed` definition vs `-NoRollbackOnHealth` | Applied (§3) |
| P7-10 | Low | Assembly-reference lists for the new assemblies; which scan enforces what | Applied (§0.2) |

**Author's verdict on round 7: P's ACCEPT stands; O's REVISE accepted in full; revision 8 written
the same night.**

## Round 8 — revision 8 (`2a20dec`), 2026-09-03, delta checks

**Reviewer O: ACCEPT** — F1–F7 applied with executable semantics, every section-2 literal
round-trips under the new rendering, the resume after `-NoRollbackOnHealth` walked and correct;
four Lows (O8-N1…N4). **Reviewer P: REVISE** — all ten round-7 Lows applied and every invariant
holding; one Medium of the revision-7 class and six Lows. Adjudicated and applied in revision 9.

| # | Sev | Finding (short) | Adjudication → revision 9 |
|---|---|---|---|
| P8-N1 | Medium | The 512 cap "per field" would fail a host-folder snapshot after ~21 releases | Applied: the cap is per scalar leaf; set/list members render one leaf per element; the B2 literal test adds a 50-release snapshot and a 14-ACE set (§8) |
| P8-N2 / O8-N4 | Low | Row 33's "current pool" tautological; child applications on the pool unseen | Applied: `Site: name?`; every application of every site using the pool is resolved and must be exactly the root application of `Site` (§5.1 row 33) |
| P8-N3 | Low | `contentType` not a closed identifier; `LocationAssertion` kinds without expected values | Applied: `mediatype` value object; `(kind, expected?)` with typed expectations and fixed literals (§5.1 grammar) |
| P8-N4 / O8-N1 | Low | `\u` escaping not injective; no decoder stated | Applied: the backslash itself is escaped; decoder defined; B2 asserts `decode(render(x)) == x` (§8) |
| P8-N5 | Low | Site/pool distinctness read across the union refuses production | Applied: each set on its own; a site and its pool may share a name (§4) |
| P8-N6 / O8-N3 | Low | Drain time undefined for workload-less writes; cancellation token vs the folder scan; timed-out guard; receipt-less `CompensationFailed` on resume | Applied: 90 s; `CancellationToken` allowed in every adapter folder; a fixed recycle whose guard timed out is skipped; the no-receipt rule (§0.2, §5.1) |
| P8-N7 | Editorial | Duplicate `AclTargetKind` sentence | Applied (§5.1) |
| O8-N2 | Low | A per-file manifest as a receipt field would hit the cap | Applied: one tree hash per host; the manifest is never a receipt field (§3) |

**Author's verdict on round 8: O's ACCEPT stands; P's REVISE accepted in full; revision 9 written
the same night.**

## Round 9 — revision 9 (`951ed89`), 2026-09-03, delta checks — **ACCEPTED**

**Reviewer P: ACCEPT** — N1–N7 applied as recorded with testable semantics; the one-tree-hash
change keeps the digest and snapshot invariants; the oracles type unchanged; four Lows
(P9-N1…N4). **Reviewer O: ACCEPT** — the four round-8 Lows applied; the revision-9 changes
verified safe against the live IIS topology (each platform site is the sole root application on
its same-named pool, both deployable pools `Started`, the docs MIME literal matches the
`mediatype` comparison); two Lows (O9-N1, O9-N2).

The six Lows were applied as an editorial pass, revision 10, without a further round (both
reviewers classed them as notes for A2): per-host tree path and byte size on the artifact
receipt (P9-N1); `Site` present iff the workload is a pool, else `INTENT-ARGUMENT` (P9-N2,
O9-N2); a fixed recycle tied to its paired `Iis.Rollback` being `Compensated`, and a
`RollbackIncomplete` resume re-evaluating the conditional fixed entries after the retries
(O9-N1, P9-N3); `mediatype` listed among the value objects (P9-N4).

**The Slice 2 specification is accepted at revision 10 (`docs/apollo-deployment-slice2-spec.md`).**
Nine rounds, 2026-09-03: draft 1 → revision 10; 51 + 33 + 26 + 27 + 32 + 22 + 17 + 12 + 6
findings adjudicated, none left open. Sub-slice A2 may start under section 9's evidence rules;
the next review is checkpoint A2+B2.

## Amendment D-S2-7 — `Precondition.Schema` observed value, 2026-09-04 (post-acceptance)

**Origin:** checkpoint C2+D2 third-round review, operations reviewer Low 3 (carried to E2):
`Precondition.Schema` rendered the raw `/health/schema` body as the receipt's `Observed`, through
the section-8 render and secret-pattern gate. A future migration id containing `token`,
`password`, `pwd=`, `secret=` or `connectionstring` inside the first 512 rendered characters would
have failed the check — and, under E2, a deployment — with `SIBYLA-S2-REDACTION` instead of a typed
receipt. The guard test `SchemaBodyRedactionGuardTests` (E2 prep) protected today's 19 ids by
convention only.

**Options put to the owner:** (a) keep the guard test and the convention; (b) render `Observed`
from the extracted ids (still tripped by an id containing a pattern); (c) render `Observed` from the
parsed values only — role check, count, SHA-256 of the body, missing count — so the text can never
contain free text. **Owner ruling 2026-09-04: (c).**

**Applied as revision 10 + amendment (no renumbering):** section 1 row D-S2-7; section 5.5 paragraph
"Observed value". Implementation on `ops/sibyla-deployment-e2-prep` under the Slice 1 evidence rule
(RED oracles before code: the source gate `SchemaObservedValueTests`, the adapter-level cases
`schema-observed-is-role-count-digest` and `schema-id-containing-a-secret-word-is-typed` on the
`ZZ.Sibyla.Probe.*` objects, the latter served by `Probe.Web` as `/schema-token.json`). The
`Observed` shape is compared by the operator, not by resume: `PreconditionReceiptV2` carries no
post-state and no compensation, so no journal or resume rule changes.
