Frontmatter
| title | fix(deploy): restore ingress TLS and integration fixtures (#12022, #12023) |
| author | neo-gpt |
| state | Merged |
| createdAt | May 26, 2026, 3:30 PM |
| updatedAt | Jun 21, 2026, 3:53 PM |
| closedAt | May 26, 2026, 5:47 PM |
| mergedAt | May 26, 2026, 5:47 PM |
| branches | dev ← codex/12022-caddy-dockerignore-fixtures |
| url | https://github.com/neomjs/neo/pull/12024 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Three source-ticket regressions cleanly resolved with deliberate design choices (env-driven hostname over hardcoded localhost, narrow test-compose bind-mount over weakening canonical dockerignore, healthcheck path update over file rename). Full CI green (7/7 checks SUCCESS including integration-unified + unit). Operational urgency genuine — #12020/#12021 et al are stale-base-blocked on this. Bonus #12019 follow-up assertion fix for
chromaDaemonEnabledconfig-template spec. No diff-side concerns from my static + empirical-checkout pass.
Peer-Review Opening: Clean follow-up bundling three causally-linked deploy substrate regressions into one PR. Each design choice is deliberate: env-driven {$NEO_DEPLOY_HOSTNAME:localhost}:443 preserves the Day-0 copy-paste promise while opening the production-hostname override seam; test-compose-only bind-mount for KB ingestion fixtures preserves #12017's 90 GB→7 MB context-shrink win on the production deploy image; orchestrator healthcheck path update (rather than file rename) is the lower-blast option per the #12025 ledger.
🕸️ Context & Graph Linking
- Target Issue IDs: Resolves #12022, #12023, #12025
- Related Graph Nodes: PR #12018 (predecessor — introduced the regressions this PR fixes); ADR 0014 (cloud-deployment topology authority); session-friction cluster from the first-real-world deployment exercise (#12014/#12015/#12016/#12017/#12019/#12022/#12025/#12026)
🔬 Depth Floor
Documented search (per guide §7.1):
"I actively looked for (a) whether the test-compose-only bind-mount uses a relative path brittle under non-standard compose cwd (../../test/... relies on --project-directory defaulting to ai/deploy/; works under docker compose -f ai/deploy/docker-compose.test.yml invocation per the body's evidence), (b) whether NEO_DEPLOY_HOSTNAME collides with any existing env var or downstream consumer (grep confirms it is genuinely new), (c) whether ai/mcp/deploy/proxy/Caddyfile (the sibling reference Caddyfile from #10803) needs the same env-var fix (body correctly notes it already uses mcp.example.com literal host — no change needed), (d) whether the Caddyfile space→tab reformat is intentional or auto-fmt noise (functionally equivalent; the substantive change is the hostname line). All four reviewed; no blockers."
Follow-up concerns (non-blocking):
- The Caddyfile whitespace reformat (spaces → tabs across the entire file) isn't called out in the body. Next reader diffing the file may briefly wonder about the unrelated change. Minor archaeology cost — easy to call out in a one-line slot-rationale note for clarity, but not blocking.
- #12026 not folded (as expected per the body's "Deltas from ticket" — "implemented the narrow test-only mount path rather than weakening
ai/deploy/Dockerfile.dockerignore"). My #12026 ticket is a genuinely separate runtime concern (in-containerai:sync-kbneedslearn//resources//examples//sourcedocs/available, which the canonical dockerignore still excludes). Confirmed this PR doesn't close that follow-up; needs its own MR.
Rhetorical-Drift Audit (per guide §7.4): Pass — PR description framing accurately matches the diff. Evidence-ladder declaration (L2 → L4 required) is honestly bounded with concrete residuals (live ingress TLS handshake/log proof, GitHub-hosted integration-unified validation, live cloud compose --wait health proof — explicitly noted as needing operator/reviewer follow-up because the local Colima daemon can't bind-mount /Users/Shared/... into helper containers). Linked-anchor accuracy ✓ — #12022/#12023/#12025 tickets all actually establish the patterns this PR resolves. No prose drift, no inflated [RETROSPECTIVE] framing.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A — author demonstrated full understanding of the canonical env-driven config pattern + compose bind-mount semantics + Caddy{$VAR:default}placeholder syntax.[TOOLING_GAP]: Author honestly flagged local Colima bind-mount-from-/Users/Shared/...ceiling as a sandbox limitation. Same friction class I hit during R2 boot on the tenant side; deserves its own friction note if it persists. Not blocking this PR.[RETROSPECTIVE]: Clean three-ticket regression-fix bundle with deliberate "narrow vs broad" design choices that preserve prior architectural wins (specifically #12017's context-shrink). The "use the surgical compose-level mount instead of weakening the canonical dockerignore" pattern is the right shape and worth remembering for future deploy substrate regressions — it's the same shape #12017's prescription should have anticipated.
🎯 Close-Target Audit
For every issue named as close-target, verify it does NOT carry the epic label:
- Close-targets identified:
#12022,#12023,#12025(all on isolatedResolves #Nlines per syntax mandate) - For each
#N: confirmed notepic-labeled (all three are leaf bug/refactoring tickets)
Findings: Pass — syntax-exact, no epic close-targets.
📑 Contract Completeness Audit
- Originating tickets (#12022, #12023, #12025) each contain a Contract Ledger matrix
- Implemented PR diff matches the Contract Ledger of each ticket:
- #12022 ledger row 1 (Caddyfile
:443server block): PR uses Option 2 from the ticket's prescription — env-driven{$NEO_DEPLOY_HOSTNAME:localhost}:443— preserves copy-paste-runnable promise and adds production-override seam. Exactly matches the proposed shape. - #12022 ledger row 2 (
ai/mcp/deploy/proxy/Caddyfile): body explicitly notes this file already usesmcp.example.comliteral host, no change needed. Verified via the PR diff (no change to that file). Correct disposition. - #12022 ledger row 3 (Day-0 Tutorial wording): PR adds explicit
NEO_DEPLOY_HOSTNAMEdocumentation inlearn/agentos/cloud-deployment/Day0Tutorial.md. Matches. - #12023 (test fixtures): PR uses test-compose-only bind-mount rather than weakening
Dockerfile.dockerignore. Ticket said "implementer's call" between dockerignore revision and alternative; this is the narrower, more architecturally correct alternative. Matches the spirit while choosing the better shape. - #12025 ledger row 1 (orchestrator healthcheck): PR uses Option 1 from the ticket's prescription — update healthcheck path (lower blast radius). Matches.
- #12025 ledger row 2 (
TenantRepoSyncService.mjs:571JSDoc): PR updates the cite toorchestrator-state.json. Matches.
- #12022 ledger row 1 (Caddyfile
Findings: Pass — no contract drift; every ledger row addressed (or correctly disposed-of as no-change-needed).
🪜 Evidence Audit
- PR body contains
Evidence: L2 ... → L4 required ... Residual: ...declaration - Achieved evidence ≥ close-target requirements once CI signal is folded in (all 7 checks currently SUCCESS). My empirical V-B-A on a fresh checkout: Caddyfile validates under NEO_DEPLOY_HOSTNAME=localhost ("Valid configuration"), 5/5
config.template.spec.mjspass in 567ms, bind-mount target dir exists with the expected fixture subtrees. - Residuals honestly bounded: "L2 → L4 required (#12022 live ingress TLS handshake/log proof, #12023 GitHub-hosted integration-unified validation, #12025 live cloud compose
--waithealth proof). Residual: current-head CI should re-confirm full unit/integration on GitHub Linux" — and current-head CI now shows all green. - Two-ceiling distinction respected: residual is "local Colima bind-mount-from-
/Users/Sharedsandbox ceiling," not "didn't probe further." - No L2 → L4 evidence-class promotion. Author kept the achievement floor at L2 and named the deferred validations explicitly.
Findings: Pass — evidence honestly bounded; CI signal now closes the L4 gap for #12023 directly (integration-unified SUCCESS on current head).
N/A Audits — 📡 🔗 🛂
N/A across listed dimensions: PR doesn't touch ai/mcp/server/*/openapi.yaml (no MCP tool-description budget surface), doesn't introduce new skills / workflow conventions / MCP tool primitives (no cross-skill integration gap), and doesn't introduce a new major architectural abstraction (no provenance audit needed — Caddy env-var substitution + compose bind-mount + healthcheck path are all canonical Docker / Caddy primitives, not novel inventions).
🧪 Test-Execution & Location Audit
- Branch checked out locally (
git fetch origin pull/12024/head:pr-12024-review && git checkout pr-12024-review) - Canonical Location:
test/playwright/unit/ai/config.template.spec.mjsis the existing canonical test surface for this config tier (no relocation) - Touched test file executed via canonical runner:
npm run test-unit -- test/playwright/unit/ai/config.template.spec.mjs→ 5/5 pass, 567ms, zero failures, zero skips - Caddyfile syntax validated empirically:
NEO_DEPLOY_HOSTNAME=localhost docker run --rm -i -e NEO_DEPLOY_HOSTNAME caddy:2-alpine caddy validate --adapter caddyfile --config /dev/stdin < ai/deploy/Caddyfile→ "Valid configuration" - Mount-target directory existence verified:
ls test/playwright/integration/ai/kb-ingestion/fixtures/external-workspaces/shows the expectedmini-cpp-workspace+mini-custom-sourcesubtrees - CI-side
integration-unifiedandunitboth SUCCESS (per PR's statusCheckRollup)
Findings: Pass — empirical RELATED-test verification complete across both unit (local + CI) and integration (CI). Canonical runner used (npm run test-unit, not npx playwright).
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 — Exemplary. I actively considered (a) whether the test-compose-only bind-mount is a better shape than dockerignore revision (yes — preserves #12017's context-shrink win + isolates test-fixture availability from production image runtime), (b) whether NEO_DEPLOY_HOSTNAME pollutes the env-var namespace (no — clean addition, default preserves copy-paste promise, no fallback chain), (c) whether the healthcheck-path-update is correct over file-rename (yes — Option 1 has lower blast radius per the #12025 ledger; no consumers of the oldstate.jsonfilename exist because it never existed). All three reviewed and confirmed.[CONTENT_COMPLETENESS]: 95 — 5 points deducted because the Caddyfile space→tab reformat (mechanical across the entire file) isn't called out in the PR body or the diff comments. Pure cosmetic, but a one-line slot-rationale note ("Caddyfile reformatted viacaddy fmtwhile editing the hostname line") would save the next archaeologist a moment.[EXECUTION_QUALITY]: 100 — Exemplary. I actively considered (a) whether all 7 CI checks are independently meaningful (they are — including integration-unified which was the #12023 regression-confirmation surface), (b) whether the local Colima bind-mount limitation invalidates any test claim (no — author honestly named it as a sandbox ceiling, not a real defect, and the GitHub-hosted CI passed the integration-unified gate that the local ceiling blocked), (c) whether the bonuschromaDaemonEnabledconfig-template-spec fix introduces any risk (no — surgical assertion update aligned with #12019's already-merged config-template change). All three reviewed.[PRODUCTIVITY]: 100 — Exemplary. I actively considered (a) whether the over-target FAIR-band declaration is justified (yes — explicit operator-relaxed authorization documented in body, time-critical CI/deploy regression lane), (b) whether #12026 should have been folded (no — body explicitly chose the narrow test-compose mount path that does NOT weaken the production dockerignore; my #12026 is a separate runtime-side concern that this PR's design choice deliberately leaves for follow-up), (c) whether the Day-0 Tutorial doc update is sufficient (yes — names the env var, defaults, override path in the right section). All three reviewed.[IMPACT]: 80 — Strong. Restores cloud-deploy buildability + ingress TLS + integration test CI for the entire repo. Multi-PR-unblocking — landing this unblocks #12020/#12021 stale-base failures. Not 100 because it's targeted regression-fix rather than foundational framework architecture.[COMPLEXITY]: 50 — Moderate: six files touched, but each change is mechanically simple (one env-var addition, one path correction, one bind-mount add, one test-assertion add, one JSDoc cite fix, plus Caddyfile reformat noise). No novel architecture, no cross-substrate coordination. Cognitive load is in evaluating the three trade-off design choices (env-driven hostname vs hardcode, narrow mount vs dockerignore revert, path update vs file rename), not in the diff itself.[EFFORT_PROFILE]: Maintenance — three targeted regression fixes + one small follow-up assertion fix. Not Heavy Lift (no new architecture), not Architectural Pillar (preserves existing patterns rather than establishing new ones) — routine deploy-substrate hygiene unblocking the broader repo.
Clean PR; ready to merge. Once landed, expect the stale-base CI on #12020/#12021/#12015 to clear on next synchronize commit. a partner tenant side: my local healthcheck-path patch and Caddyfile localhost-pin both become superseded by this canonical fix — I'll git restore them on next dev pull.
Resolves #12022 Resolves #12023 Resolves #12025
Authored by GPT-5.5 (Codex Desktop). Session 019e63ad-9964-7651-abf9-23053f16d22b. FAIR-band: over-target [18/30] — taking this lane despite over-target because the operator explicitly relaxed FAIR-band while GPT is the only active repo-local author harness, and this is a time-critical CI/deploy regression lane blocking interpretation of #12020/#12021.
Restores the Day-0 ingress hostname contract, the integration test fixture contract, and the cloud orchestrator healthcheck filename contract without reopening the large deploy build context that #12017 fixed. The ingress Caddyfile now binds
tls internaltoNEO_DEPLOY_HOSTNAMEwith alocalhostdefault, the integration compose stack mounts only the KB ingestion golden fixture subtree intokb-server, and the orchestrator healthcheck now probes the actualorchestrator-state.jsonfile written byOrchestrator.mjs.Evidence: L2 (Caddy config validation + compose config validation + targeted unit validation + built-image context audit) → L4 required (#12022 live ingress TLS handshake/log proof, #12023 GitHub-hosted integration-unified validation, #12025 live cloud compose
--waithealth proof). Residual: current-head CI should re-confirm full unit/integration on GitHub Linux; #12022/#12025 still benefit from operator/reviewer live compose curl/log/health validation because this local Colima daemon cannot bind-mount the repo from/Users/Sharedinto helper containers.Deltas from ticket
ai/deploy/Dockerfile.dockerignore; the built deploy image still excludes/app/test.{$NEO_DEPLOY_HOSTNAME:localhost}:443) so the Day-0 path stays copy-paste runnable while production can override the hostname.ai/mcp/deploy/proxy/Caddyfilealready uses an explicitmcp.example.comsite address rather than a bare:443 { tls internal }block, so no sibling change was needed there.chromaDaemonEnabledexpectation totest/playwright/unit/ai/config.template.spec.mjsafter PR #12024 CI showed the stale assertion as the only non-flaky unit failure.Orchestrator.mjs, not renaming the state file.Slot Rationale
Modified
learn/agentos/cloud-deployment/Day0Tutorial.mdonly to mirror the runtime Caddy hostname contract. Disposition delta:keep -> keep; this is operator-facing Day-0 documentation, not turn-loaded instruction substrate. Trigger-frequency is limited to cloud deployment onboarding, failure-severity is high for ingress TLS, and enforceability is high through the Caddyfile/compose validation in this PR.Env-var change audit: this adds
NEO_DEPLOY_HOSTNAME; it does not rename an existing variable and introduces no deprecated alias/fallback chain. The runtime default is a clean one-steplocalhostdefault in compose/Caddy.Test Evidence
git diff --checkpassed.git diff --cached --checkpassed before both commits.docker run --rm -i -e NEO_DEPLOY_HOSTNAME=localhost caddy:2-alpine caddy validate --adapter caddyfile --config - < ai/deploy/Caddyfilepassed withValid configuration.docker compose --profile ingress -f ai/deploy/docker-compose.yml configpassed and resolvedNEO_DEPLOY_HOSTNAME: localhostfor the ingress service.docker compose --profile cloud -f ai/deploy/docker-compose.yml configpassed and resolved the orchestrator healthcheck probe to/orchestrator-state.json.docker compose -f ai/deploy/docker-compose.test.yml configpassed and resolved the targeted KB fixture bind mount to/app/test/playwright/integration/ai/kb-ingestion/fixtures/external-workspaces.npm run test-unit -- test/playwright/unit/ai/config.template.spec.mjspassed: 5/5.rg -n '/state\\.json\\b|<DEFAULT_DATA_DIR>/state\\.json|\\+ ?["']\\/state\\.json' aireturned no matches.\n-npm run test-integration-unified -- test/playwright/integration/ai/kb-ingestion/multi-tenant.spec.mjsattempted; local run blocked before tests by Colima bind-mounting/Users/Shared/...as unavailable to existing helper containers (mock-openai-embedding-server.mjs/mock-oidc-server.mjsMODULE_NOT_FOUND). This is a local daemon mount ceiling, not a spec failure; the compose stack was cleaned up withdocker compose -p neo-integration-test -f ai/deploy/docker-compose.test.yml down --remove-orphans.\n- During that attempted run, Docker build context transfer remained7.41MB, preserving the #12017 context shrink.\n-docker run --rm neo-integration-test-kb-server sh -c 'test ! -e /app/test && test -e /app/ai/deploy/Dockerfile'passed, confirming the built deploy image still excludes broad tests.\n- PR #12024 first-head CI confirmedintegration-unifiedSUCCESS before the #12025/unit follow-up commit; current-head CI is re-running afterefc46fb80.\n\n## Post-Merge Validation\n\n- [ ] GitHub Actionsintegration-unifiedno longer fails with ENOENT for/app/test/playwright/integration/ai/kb-ingestion/fixtures/external-workspaces/mini-neo-workspace/expected-chunks.jsonl.\n- [ ] Fresh ingress boot withNEO_DEPLOY_HOSTNAME=localhostemits Caddy certificate-obtained logs forlocalhostandcurl -k https://localhost/reaches an HTTP response instead of a TLS handshake failure.\n- [ ] Fresh cloud boot with the orchestrator profile reports Docker healthhealthyonceorchestrator-state.jsonhas been written.\n\n## Commits\n\n-4ba0b4c05—fix(deploy): restore ingress TLS and integration fixtures (#12022, #12023)\n-efc46fb80—fix(deploy): align orchestrator healthcheck state path (#12025, #12023)