Skip to content

release: dev -> main — deep-dive audit fixes, middle-tier gating - #376

Merged
LeXwDeX merged 69 commits into
mainfrom
dev
Aug 19, 2026
Merged

release: dev -> main — deep-dive audit fixes, middle-tier gating#376
LeXwDeX merged 69 commits into
mainfrom
dev

Conversation

@LeXwDeX

Copy link
Copy Markdown
Owner

Release promotion: dev → main

What's in this batch

67 commits since graphagent-v1.0.26 (PR #339), delivered through the SpecGit harness — every PR merged with specgit finish exit 0:

Deep-dive audit fixes (issues #340#350)

Platform & harness

Test summary

Release: on merge, run the 📦 Release · Fork Build workflow from main (official, non-prerelease).

fix(opencode): auto-update notifies only; latest() tracks fork release feed
- spec_git/policy.yaml: required check 'Typecheck' (dev-layer gate parity)
- specgit-accept.yml: PR-to-dev acceptance evaluation
- .opencode/command: /specgit-issue, /specgit-finish entry points
- .opencode/hooks/specgit-merge-guard.sh: merge guard script (wired by specgit setup)
- AGENTS.md/CLAUDE.md: managed specgit block
Closes#353
specgit issue regenerated the harness with the repo default branch (main);
delivery PRs in this repo target dev (layered gating), so the trigger must
be [dev].
Generated harness assumed the SpecGit repo's own pnpm toolchain. This repo
is a bun workspace without packageManager/pnpm-lock: setup-node + npm i -g
specgit, minimal policy.yaml parsing without a yaml dependency.
chore(specgit): enable SpecGit delivery harness
Five-track deep-dive closing the 2026-08-18 audit's limitation gaps:
#316 trigger-source proof (closure rationale), loop.ts full re-read,
DAG unaudited files + 4 invariants, out-of-package composition roots,
effect-smol v4 semantics verification. 12 findings filed as #340-#350.
Closes#355
docs(audit): add 2026-08-19 deep-dive audit report
GoalLoop's only consumer is InstanceBootstrap's serviceOption(GoalLoop.Service)
.init(), which runs in the request fiber's ambient context. GoalLoop.node was
listed only in AppLayer, so headless serve/web, the desktop sidecar, and
non-CWD TUI directories never armed the idle-event subscription or the startup
goal scan — standing goals stalled after their first turn (the same wiring
class as SettingsHook/Memory in #311).
Closes#340
fix(goal): wire GoalLoop.node into the server request-context app graph
The host-level deadline supervision sweep was constructed only in AppLayer;
the desktop sidecar calls Server.listen without effectCmd, so AppLayer never
existed there and orphaned-node supervision never ran on the desktop default
path. Every serving process builds the server app graph once per listener —
listing the sweep node here covers serve/web/TUI/sidecar alike. Processes that
also build AppLayer get a second instance; settle is convergent (workflow
lock + guardNode + conditional projector UPDATE), so the duplicate is safe.
Closes#341
fix(dag): build DagSupervisionSweep in the server app graph
The window derived solely from the current persisted config; replan can lower
a running node's timeout_ms while the A1/Q2 re-time gate keeps the old watcher
on its old cadence, so a config-only window could be shorter than the live
watcher's cycle and prematurely settle a healthy node. deadline_ms is only
ever written as grant-time + timeout (spawn and each extension; escalations
move only the counter), so (deadline - started_at) upper-bounds the watcher's
current cadence in every shape. Window = max(config cadence, durable bound).
Closes#342
fix(dag): sweep freeze window covers the live watcher's actual cadence
… a host-level settle
With the owning instance torn down, checkCompletion/dag.fail/complete and the
automation unregister all lived inside the dead DagLoop — a swept workflow
stayed running forever with required nodes failed and its lease registration
leaking (workflow rot). After a host-level settle lands, the sweep now mirrors
checkCompletion's durable half: when every current-revision node is terminal
it fails (required-node failure) or completes the workflow via the same
workflow-lock-serialized command layer, then unregisters the dag lease.
Parent wake delivery stays with the owning instance (session context) and
converges through the DagLoop init drain on the next instance load; cascade
skips of pending dependents likewise converge at adopt.
Closes#343
fix(dag): sweep terminalizes workflows and releases their lease after a host-level settle
The handler asserted the payload as WorkflowConfig and called dag.create
directly — create runs only structural checks (safe only when authoring has
vetted the graph), so the checkpoint gate, output_schema obligations on gated
checkpoints, worker/model/prompt asset resolution, and server-side deep-mode
admission minting were all bypassed over HTTP. The handler now wraps the
payload as an inline StartSpec and runs authoring.prepare(environment profile)
through the same shared catalog loader the workflow tool uses (extracted to
dag/environment-catalogs.ts); validation failures surface as 400 with the
diagnostic list.
Closes#344
The lint budget is exactly at its cap (4850); the two now-unused imports
pushed CI one warning over.
fix(dag): httpapi dag.start passes Workflow Authoring
The recovery path completed a schemaless running node with undefined and
never read the child session's messages, while the live path settles with the
last assistant text — a crash between the child's final reply and the
NodeCompleted publish erased the outcome: a bare {"verdict":"replan"}
checkpoint reply lost its veto (no pause, no warning) and gated dependents
resolved no fields. Recovery now mirrors the live path via an injected
last-assistant-text reader; an explicitly unparseable workflow row (null) fails
the node loudly instead of undefined-completing past settleCapturedOutput.
Closes#345
fix(dag): crash recovery preserves a schemaless node's string verdict
chore(specgit): pin the acceptance workflow's specgit CLI to ^0.5.0
The file shipped in #361 without the AGPL copyright/license header; the core
license-scope manifest test has kept dev's push-triggered full run red on every
merge since 698713c.
Closes#368
fix(license): add the SPDX headers to environment-catalogs.ts
The Typecheck-only PR->dev gate let an assertion-level regression (#368,
missing SPDX headers) merge and keep dev's push-triggered full run red for 75
minutes. ci-test.yml now triggers on PRs to dev (the whole matrix runs, but
only Unit Tests (linux) becomes required); spec_git/policy.yaml follows so
specgit finish enforces the same pair. E2E stays push-on-dev + dev->main.
Closes#370
…e tool
The new PR->dev unit gate caught it (#371's own run): the exerciser's CI
context has no resolvable model, and the environment-profile validation made
model.unavailable a blocking 400 — while the workflow tool's start action
asks a question instead (an HTTP caller has no such interaction; spawn fails
loudly at execution time if a model never resolves). All other diagnostic
classes stay blocking.
… a model-bearing session
CI (no providers) exposed two layers: the advisory filter let a graph that
never compiled (prepared === undefined) through to a confusing 'no prepared
graph' 400 — a non-compiling graph is now always blocking regardless of
diagnostic class; and the dag.start happy-path scenario relied on the
pre-#344 behavior of never resolving a model. The scenario now runs under
withLlm with an explicit session model so the parent resolution chain has
something to resolve in the provider-less CI environment.
…28min)
The verdict waits for every policy check to reach a terminal state; with
Unit Tests (linux) now required on dev PRs, the 15min job cap timed out
while the sibling was still running.
…was the real timeout
The job-level 45min bump was necessary but not sufficient: the sibling-wait
script carries its own hardcoded 15-minute deadline and gives up while Unit
Tests (linux) (~28min) is still running.
- NEW-1: spawn failure boundary rethrows interrupts (parity with the file's
own catchCause discipline) instead of persisting a nodeFailed
- NEW-2: recovery-pause retries twice and only abandons adoption when the
durable row is genuinely terminal (mirrors the replan-verdict gate)
- REC-1: pending-node stale-child cancel is cause-hardened like the running
branch — a persistent failure no longer aborts the whole reconcile
- BLK-02: hyphen/underscore writer-id key collisions rejected at compile time
- BLK-03: a verify node serving multiple parallel-writer review routes is
rejected (the contract binds ONE fingerprint) instead of silently mapping
only the first aggregator
- CAP-02: regex tests capped at 100k chars, uniqueItems scan capped at 1000
items, file-ref capture capped at 64MiB (inline fallback beyond)
- SW-L1: sweep publishes stamp the workflow row's location so live instances'
summary publishers push swept settles to the TUI
- F3: TUI reconnect refreshes stored goals (a missed goal.cleared no longer
leaves a stale sidebar indefinitely)
- F6: dag.cancel.active added to the keybind Definitions/CommandMap
- AGENTS.md: tool-call discipline rules (no duplicate fan-out queries, one
watch per CI wait)
SW-L2 (cancel dies without ambient instance — acknowledged design) and F4/F5
(record items on the event surface) are accepted as designed.
Closes#349
… Convention
CONTEXT.md claimed an engine-enforced invariant; the implementation is
orchestrator guidance only (dag.create and the tool start accept a session
with a live workflow; the wake model and lease tolerate multiples). The
divergence was the defect (audit INV-A) — this records the truth: a modeling
convention with documented bounded consequences, including the cross-workflow
write-set caveat. Engine enforcement remains available as a future decision
if the convention proves insufficient.
Closes#348
chore(ci): raise the dev PR gate to Typecheck + Unit Tests (linux)
…s the reconcile
The old test pinned the abort-on-cancel-failure behavior the audit flagged as
the REC-1 defect (the workflow became unadoptable in-process until restart).
fix(dag): low-severity batch from the 2026-08-19 deep-dive audit
docs(dag): 'one objective, one live DAG' downgraded from Invariant to Convention
…view nits
Two-axis review of today's 62-commit range (both axes PASS, info findings):
- AGENTS.md gate prose/table updated to Typecheck + Unit Tests (linux) (#370 drift)
- CONTEXT.md: Conventions section moved after the full Invariants list — the
#348 edit had demoted 5 original Invariants (incl. the engine-enforced
gated-checkpoint obligation) under the new header
- memory-search: identical if-branches folded back to ||
- blocks.ts: BLK-02 collision check extracted from an IIFE into
aggregatorEvidenceMapping per the Style Guide
- #340's serve-mode behavioral e2e documented as a covered-by-composition
delta (e2e-loop behavior tests + the wiring probe for the failure class)
Closes#374
chore: post-review cleanup — docs drift, CONTEXT.md section order, review nits
main's legacy branch protection requires the SpecGit Acceptance check, but
the workflow only triggered on PRs to dev — the dev->main release PR was
permanently BLOCKED with all four Ruleset checks green.
chore(specgit): acceptance also runs on PRs to main
@LeXwDeX
LeXwDeX merged commit dac5d3a into mainAug 19, 2026
16 of 17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LeXwDeX