---ci--- phase: 3 milestone: v0.2 status: complete requirements: covered: [REQ-21, REQ-22, REQ-23] partial: [] ---/ci---
20 KiB
CIAgent Grill Report
Run: 2026-10-10 (mode: interactive, focus: v0.2 plan red-team — cli-packaging)
Supersedes the v0.1 grill in this file (v0.1 run of 2026-10-09; its full text and
verdicts G-001..G-008 live in git history at 7f7868f and remain binding where the plan
depends on them: G-005's owner-run post-ship OWUI smoke checklist and G-008's 90-day
adoption criterion carry forward in ROADMAP.md §Post-Milestone Notes; G-007's
forge-repo-creation test was executed at v0.1's P0 ship and is closed). New verdicts
number G-009 onward.
Verdict (summary): GO-WITH-CONDITIONS (confidence: 0.82)
The plan rests on genuinely strong research — live-verified forge/registry wire shapes, a
working zipapp prototype booted against the real broker sources, zero-broker-lines
verified against the actual file (1390/1400). Red-team findings are concrete and
fixable by task/ship-step edits: one unexecutable mechanism (the v0.2.1 attachment
bridge cites a harness path RESEARCH itself proved absent from this environment),
one ship-sequence inversion (P3-05 attaches the release before the tag exists —
gitea would mint v0.2.3 at the wrong commit), one false-green determinism gap
(the normalize pass "preserves" umask-derived zip attributes, so must-have 4 passes
while the "clean checkout" exit criterion silently fails cross-machine), and a set of
test-realism holes (subprocess harness with no timeout discipline, missing creds env
in the packaged secret-read, no live read-only validation before the one live publish).
Three forks were resolved interactively with the owner this session (G-011, G-013,
G-014); everything else is bound on evidence below.
Surface 1 — Determinism claims (must-have 4; P1-01)
Forcing question: Is two-build sha256 equality actually achievable and testable, and what does the normalize pass fix vs. miss?
- Evidence (stdlib source, read this session):
/usr/lib/python3.11/zipapp.pybuilds viafor child in source.rglob('*'): z.write(child, arcname.as_posix())— entry order = scandir order, entry mtime = source mtime (ZipInfo.from_file), andexternal_attr= the staging file's stat mode<< 16. PLAN P1-01's normalize pass fixes order (sorted),date_time=(1980,1,1,0,0,0),ZIP_DEFLATED— but specifies "preservedexternal_attr". Preserved from what zipapp wrote, i.e. derived from staging file modes — which are0666 & ~umaskif staged via plain copy, or checkout-mode if viacopy2. Umask 0022 vs 0002 across machines/clones → differentexternal_attr→ different sha256, while must-have 4 (two consecutive builds, same machine) still passes. A silent false green against the phase exit criterion ("builds reproducibly from a clean checkout"). - gzip is not in the pyz path (zipfile DEFLATE only). Image-layer gzip is
deterministic on-platform:
gzip.compress(..., mtime=0)routes tozlib.compress(wbits=31)on 3.11 (verified in gzip.py source) — fixed header, fixed OS byte, stable per zlib build. Scope claim: byte-identity is same-environment (same machine, same zlib); cross-zlib-version identity is neither claimed nor required by must-have 4. - Answer: achievable and testable as scoped — provided the normalize pass also fixes what it currently preserves. → G-009.
Surface 2 — Boot parity via subprocess (P1-02)
Forcing question: What makes the subprocess acceptance flaky, and what's missing?
- Evidence: zero v0.1 tests use
subprocess/Popen(grep overtests/: no hits) — P1-02 introduces the repo's first subprocess-boot harness. The plan's env enumeration (BROKER_BIND,BROKER_ENV_PATH/DB/AUDIT) omitsBROKER_CREDS_FILE/BROKER_CREDS_SOURCEwhile requiring "secret-read reuse the v0.1 acceptance request patterns" — the v0.1 precedent (tests/test_acceptance_full.py:76-77) sets both;FileCredsSourceon a missing file raises_SecretNotFound→ 404 (coreci_broker.py:440-441), so the packaged secret-read would fail.Popen(["python3", ...], env={minimal})leaves the child withoutPATH(works today via execvp's confstr fallback — by accident). No timeout discipline on "read stdout lines untilcoreci-broker listening on" (RESEARCH §4.3) — a failed boot never prints the line and hangs the suite. "Pass printed exactly once" is asserted against a still-running server; shutdown-path output is unread unless drained after terminate. And every planned boot pins absolute state paths — the owner's literal happy path (download →python3 *.pyzin some directory →.env/db/audit materialize in the CWD) is never tested. - Answer: harden the harness — the plan's claims are all testable once the harness is honest about them. → G-010.
Surface 3 — One-shot lead upload bridge (P1-03)
Forcing question: Is the v0.2.1 ad-hoc attachment a discipline violation, and is it even executable as specified?
- Evidence: PLAN P1-03 attaches via "the same harness mechanism v0.1 used for its
broker-vX.Y.Z.tar.gzassets" — but RESEARCH §2.2 (the plan's own research base) found that mechanism is the ship harness'scoreci build/package/release uploadCLI, "not installed in this dev environment"; v0.1's archives were produced container-side, ad-hoc, with no repo-owned script. The bridge as written is therefore unexecutable via the cited mechanism, and its realistic substitute is an untested hand-rolled live-forge write (multipart encoding, token handling, idempotency) at first live use — performed by lead-developer, the one persona barred from code writes, using token discipline thatscripts/_env_secrets.py(which does not exist until P2) is supposed to enforce. This is precisely the practice REQ-22 formalizes away. - Owner decision (this session): restructure — release automation moves into P1; the bridge is deleted. → G-011. The hard gate question dissolves: v0.2.1 ships script-driven; there is no bridge to become permanent.
Surface 4 — Daemonless OCI publish (P2-02, P3-05)
Forcing question: Does the milestone ship sequence hold up when publish fails after the tag exists — and does it even run in the right order?
- Evidence — sequence inversion: P3-05 as written runs
release.pyattach → publish image → squash-merge →main→ tagv0.2.3→ push. ButPOST /api/v1/repos/ {o}/{r}/releaseswith a missing tag creates the tag attarget_commitish(default-branch head) — gitea's documented CreateReleaseOption semantics (schema in RESEARCH §2.1). A create-first flow pre-merge would mintv0.2.3pinned to pre-merge main, then collide with (or silently diverge from) the harness's own tag push. The plan's own P2-04 sequences correctly (tag → merge → push → release) — P3-05 alone inverts it. - Evidence — wire discipline: bounded retry(3) is specified but no per-request
timeout anywhere; a hung 29.8 MB monolithic POST through a throttled gitea turns
"bounded retry" into an unbounded hang. The 307/308 stub test proves only
Location-following, and gitea's
Locationmay be relative (RESEARCH §3.3) — the harder case is untested. Upstream3.11-slimdrift is handled (resolve-at-publish +--base-manifest+ provenance digests; RESEARCH §3.7) and the anonymous-200 token trap fails loudly per spec. - Evidence — failure story: "publish never blocks merge" is honored, but when
publish fails after
v0.2.3exists, the plan records onlyrelease_pending— no named post-milestone re-run/closure step. The ship contract makes the escalation auto-resolution; somebody must own the retry. - Answer: reorder P3-05, add timeouts + relative-Location coverage, and make the escalation carry its own closure runbook. → G-012, G-019, G-020.
Surface 5 — Scope discipline (P2-03, REQ-22/23)
Forcing question: Is anything decorative, and is anything REQ-22/23 implies missing?
- Evidence — decorative risk: P2-03's verify is presence-only ("
Dockerfile+ README sections present") and the dev environment cannot build the Dockerfile (no runtime) — a syntactically or semantically broken Dockerfile passes the gate. The "equivalence mapping" header is documentation with no enforcement. - Evidence — missing: no
.dockerignoreanywhere in the plan —docker build .ships the whole repo (.git/, any local.env*,data/,*.db,dist/,__pycache__/,.ciagent/) as build context, adjacent to a security review that asserts "no secrets in any image layer". REQ-22's notes model is covered (§2.3, stub asserts sections present); the sha256's source is unspecified (hand-pasted vs. computed);latestsemantics are coherent (once per milestone at final ship — v0.2.3 is the first image ever, D-021); the state-dir volume contract is documented (WORKDIR /state, README docker run). - Answer: make the Dockerfile contract testable, add
.dockerignore, pin digest provenance. → G-015, G-016, G-017.
Surface 6 — Frozen broker / shim dependency risk (P1-01)
Forcing question: Does the shim need broker cooperation, and what is the coupling risk?
- Evidence: RESEARCH §1.2's prototype booted from the real sources with zero broker
cooperation — re-verified against the file this session:
run_standalone(coreci_broker.py:1369),_parse_env_file(:154),boot(:598),load_config(:240) are module-level;load_configprecedence (process env →.env→ defaults, :246-257) makes the shim's pre-setos.environapproach contract-identical. The broker's.envresolution is CWD-relative by default (ENV_PATH_DEFAULT=".env",ENV_DEFAULTS:53-66) — packaged boots with unsetBROKER_ENV_PATHanchor state to the CWD, which is exactly the documented happy path and is currently untested (folded into G-010). Residual risk: the shim calls_parse_env_file— an underscore-private symbol. Within v0.2 the broker is frozen so the risk is nil; across future milestones a rename breaks the shim with a loud ImportError (acceptable), but only if somebody knows to look. - Answer: no cooperation needed (verified twice); the private-API dependency must be documented and review-tracked, and the default-anchoring boot must be tested. → G-018 (anchor case bound under G-010).
Surface 7 — Test realism: stub fidelity (P2-01/P2-02 tests)
Forcing question: What would a stub MISS that the real forge does — and is the as-built client ever validated against the real forge before its one live write?
- Evidence: the stub coverage is unusually good (anonymous-200 trap, index→amd64
resolution with Accept check, HEAD-skip, monolithic+two-step uploads, 307-following,
digest and diff_id assertions, both tags; multipart field/
?name=byte assertions, idempotent re-attach, 500×3 marker, token discipline). Gaps: fixtures are frozen at research-time shapes (2026-10-10) while upstream index and forge auth can move mid-milestone; the 307 test doesn't pin the relative-Locationcase (bound in G-019); gitea's 4 MB attachment cap is out-of-band (pyz ≈30 KB — immaterial, noted); and no step anywhere validates the as-built scripts against the realgit.coreci.devin read-only mode — the first live run ofpublish_image.pyis the P3-05 milestone publish itself, the one shot per milestone. - Owner decision (this session): add an env-gated, read-only live smoke as a P3-05 pre-ship verify step. → G-013.
Surface 8 — Wave ordering & effort (P2 packing)
Forcing question: Is P2 overloaded, and are there hidden dependencies that break the wave order?
- Evidence: original P2 packs
_env_secrets+release.py+publish_image.py+Dockerfile+ README + 2 test files into 3 waves — the overload is real, and the bridge finding (Surface 3) is the reason it dissolves: release automation belongs in P1 (G-011), leaving P2 = publish client → Dockerfile/docs → ship. Hidden dependencies check out:publish_image.pyimports_env_secrets(lands earlier in P1 now — order preserved transitively); the Dockerfile task consumes P1-01's staging allowlist (G-016 formalizes the contract as a test).publish_image.pyremains the plan's biggest single commit even after the restructure. - Owner decision (this session): keep it one vertical task — the pull/push seam is artificial, the stub fixtures are shared end-to-end, RESEARCH §3.4 is near-pseudocode, and a split would not isolate the actual risk (first live write — covered by G-013). → G-014.
Binding Decisions
| ID | Surface | Decision | Binds | Confidence |
|---|---|---|---|---|
| G-009 | 1 | Normalize pass must SET constant external_attr (files 0o100644<<16, dir entries 0o40755<<16), not "preserve" staging-derived modes; staging is an explicit allowlist (coreci_broker.py, coreci_broker_client.py, coreci_broker_ui/**, shim staged as root __main__.py, generated _cli_version.py) — never a repo rglob, __pycache__/dist/ excluded by construction. Byte-identity scope = same environment (same zlib/machine); cross-zlib identity is not claimed. |
P1-01 task edit | 0.85 |
| G-010 | 2 | P1-02 harness hardening: subprocess boots use sys.executable (absolute); child env adds BROKER_CREDS_SOURCE=file + BROKER_CREDS_FILE=<tmp fixture> (v0.1 precedent test_acceptance_full.py:76-77); deadline-bounded wait for the listening line (~30s → fail with captured stdout+stderr, terminate child); pass-once/silent-second-boot assertions completed by draining stdout after terminate; ADD a default-anchoring boot case (cwd=<tmp>, no state overrides beyond BROKER_BIND) asserting .env/broker.db/broker.audit.log in the CWD — the owner's literal happy path. |
P1-02 task edit | 0.85 |
| G-011 | 3 | Restructure (owner, this session): move scripts/_env_secrets.py + scripts/release.py + tests/test_release_upload.py into Phase 1 as new wave 3 (P1-03 · release-asset automation); old P1-03 ship renumbers P1-04 and ships v0.2.1 script-driven (build_cli.py v0.2.1 → release.py v0.2.1 <pyz>); the one-shot lead-upload note is DELETED (its cited mechanism is absent from this environment — RESEARCH §2.2). P2 renumbers: publish_image.py → P2-01 (wave 1), Dockerfile+docs → P2-02 (wave 2), ship → P2-03 (wave 3). Four-test-file count unchanged. |
PLAN edit (before P1-01 executes) | 0.90 |
| G-012 | 4 | P3-05 sequence corrected: squash-merge → main → tag v0.2.3 → push → THEN release.py attach (GET-by-tag finds the real tag; no pre-merge release create) → publish image → closeout. Mirrors P2's own correct ship order; eliminates the gitea create-tag-at-target_commitish hazard. |
P3-05 ship-step edit | 0.80 |
| G-013 | 7 | Env-gated live read-only smoke (owner, this session): pre-publish P3-05 verify step — gitea GET /v2/ 401 challenge shape, GET .../releases/tags/{tag}, GET /v2/token Basic → non-empty JWT, Docker Hub anonymous token + 3.11-slim index→amd64 resolve. Implemented as a --check/verify mode on the scripts or scripts/verify_endpoints.py; GET-only, never in the default unittest run; tokens only via _env_secrets. |
P3-05 verify + task edit | 0.85 |
| G-014 | 8 | publish_image.py stays one vertical task (owner, this session): shared stub fixtures, RESEARCH §3.4 near-pseudocode; a split would not isolate the real risk (first live write — G-013 covers it). | PLAN shape (no edit) | 0.75 |
| G-015 | 5 | .dockerignore added to the Dockerfile task: excludes .git, .env*, data/, *.db, *.log, dist/, __pycache__, tests/, .ciagent/. Build-context hygiene; P3-02 scope gains "no secrets in the build context" alongside "none in any layer". |
P2-02 task edit | 0.85 |
| G-016 | 5 | Dockerfile equivalence made testable: the Dockerfile task adds a consistency test (into tests/test_build_cli.py) asserting COPY source set == build_cli.py staging allowlist, identical zipapp flags (--python "/usr/bin/env python3" --compress), identical _cli_version.py generation format, ENTRYPOINT/WORKDIR == publish_image's config.Entrypoint/config.WorkingDir. Presence-only verification no longer gates a possibly-broken Dockerfile. |
P2-02 task edit | 0.80 |
| G-017 | 5 | Digest discipline: the sha256 in release notes is computed by release.py from the artifact file itself — never a CLI argument, never hand-pasted. Digest/asset mismatch becomes structurally impossible. |
P1-03 task edit | 0.85 |
| G-018 | 6 | Shim private-API dependency tracked: cli/__main__.py header documents the coupling to coreci_broker._parse_env_file + run_standalone ("private-API dependency — any future broker refactor must re-run the v0.2 packaged acceptance"); P3-02 review scope lists it. Loud-ImportError breakage accepted. |
P1-01 task edit + P3-02 scope | 0.75 |
| G-019 | 4 | Publish-client wire hardening: explicit per-request timeouts on every registry call (generous on blob uploads, ~120s); blob-level bounded retry with HEAD-skip between attempts (a timed-out 29.8 MB POST resumes cheap); the 307 stub test uses a RELATIVE Location at least once (gitea may answer relative — RESEARCH §3.3; absolute is the easy case). |
P2-01 task edit | 0.80 |
| G-020 | 4 | Publish-failure closure is a named step, not an open escalation: the release_pending entry records the exact re-run command (python3 scripts/publish_image.py on main @ v0.2.3; --base-manifest optional pin) and its close condition (both tags visible in gitea packages). "Never blocks merge" unchanged. |
P3-05 ship-step edit | 0.80 |
| G-021 | 4 | The v0.2.0 (phase-0) release carries NO .pyz and its notes must not promise one — D-020 starts at v0.2.1 (build tooling first exists in P1). Prevents a D-020 misread at the phase-0 ship immediately following this grill. |
Phase-0 ship note | 0.85 |
Escalations
- (none open — the three material forks were resolved interactively with the owner this session: G-011 restructure, G-013 live smoke, G-014 task packing. G-001..G-008 from v0.1 remain binding per git history; G-005/G-008 carry forward in ROADMAP post-milestone notes unchanged.)
Promotion note (explicit, not performed by the grill)
G-011 shifts REQ-22's attach automation into Phase 1; ROADMAP.md's Phase 2 description ("release-asset automation" wording) and Phase 1's requirements line need a one-line touch-up when the PLAN restructure is promoted — by the orchestrator/owner via the normal plan-revision path, not by this file. Nothing else in PROJECT/REQUIREMENTS/ROADMAP is affected by these verdicts.
Overall Verdict
GO-WITH-CONDITIONS — confidence 0.82.
The plan is research-grounded to an unusual degree (live wire shapes, working prototype, zero-broker-lines verified against the file), and every finding is actionable by task or ship-step edit. It is GO only with all of the following applied:
- G-011 restructure applied to PLAN.md before P1-01 executes — the bridge is unexecutable as written; v0.2.1 must ship script-driven.
- G-009 + G-010 applied to P1-01/P1-02 — determinism must fix (not preserve) entry attributes; the subprocess harness needs interpreter-path, creds-env, timeout, drain, and default-anchoring discipline.
- G-012 + G-020 applied before P3-05 — attach-after-tag ordering and the publish-failure closure runbook are ship-contract corrections, not optional polish.
- G-013 live smoke wired as the P3-05 pre-ship verify step — the one live publish must not also be the first live wire contact of the as-built client.
- G-015/016/017/018/019 applied with their tasks — context hygiene, testable Dockerfile contract, digest provenance, tracked private-API coupling, wire timeouts.
- G-021 noted at the phase-0 ship — v0.2.0 carries no
.pyz.