- peer_messages.row_get gains default=None (was None-only); the
three old variants agree on all real row types (None / dict /
sqlite3.Row): auto_memory's old default was already None, and
lifecycle's old None special-case and dict .get give identical
results on those types, so no call-site behavior changes and no
call site passes a non-None default.
- auto_memory and lifecycle delete their private _row_get copies
and import peer_messages.row_get as _row_get (precedent: server,
worker, handler_project, handler_support already do this);
peer_messages imports no cloud modules, so no import cycle.
lifecycle's old broad-Exception catch differs only for pathological
__getitem__ inputs, which are not DB rows and are out of contract;
this is documented on row_get, not left as a second story.
- Scope note: other modules' own row_get copies (approvals,
automations, marker_tools, todos, wait_ops) are out of this item's
stated scope and are untouched.
- Also drops auto_memory's unused 'import os' (pre-existing F401),
so touched files are ruff E,F clean.
- Tests: cloud/tests/test_row_get_unified.py locks the identity
(both aliases ARE row_get), the real-row-type matrix, the
None-default fallback, and snapshot_to_dict via the unified path.
(cherry picked from commit 1644a0b837bfefb13cdc3c93d87fc1b246026607)
- model_breakdown inferred params as a 1-tuple from the first
branch, so params + (month,) was tuple[Any] vs tuple[Any, str].
Annotate params as bare tuple (tuple[Any, ...]) before branching.
- Annotation only: the () initializer is never used (if/elif/else
always overwrites it), runtime semantics unchanged.
- mypy cloud/: 0 errors in 94 files (was 1 error); ruff clean;
cost tests 39 pass.
(cherry picked from commit a9fd0692361e8169554413b725c81135be2c5525)
- New production caller cloud/worker_sweep.py (python -m
cloud.worker_sweep): AuthStore.authenticate + CODEX_GLOBAL_ADMINS
global-admin check (same fail-closed semantics as
handler_admin._is_global_admin) before any reap call; denied
authenticated non-admins get an admin_worker_sweep_denied audit
row; authorized runs reuse reap_stale_workers (classify, re-verify,
pre-kill audit fail-closed, per-pid SIGKILL only, post-kill
verification) plus an admin_worker_sweep summary audit; --dry-run
lists classifications only.
- No new HTTP route, no container enumeration, no name/prefix
matching, no workers-row deletion; workers.py docstring only notes
the new caller.
- Tests: cloud/tests/test_worker_sweep_admin.py, 6 cases (bad key,
non-admin, unconfigured admin list fail-closed, authorized full
chain, dry-run, CLI main authorized/refused).
(cherry picked from commit c7d217549c5ccedf61dcc924b8998fffeddc76c6)
process_audit_verdict now calls validate_signer_separation
before any state save or ledger write (previously the function
had no production caller on this path: it only ran inside
collect_failed_checks via decide_pushback, which itself has no
production caller). Signer is bound to the authenticated
caller -- a body signer differing from the caller is rejected
-- and executor/coordinator_approver/qa_tester must be
declared and distinct from the caller; missing field,
self-audit, or forged signer raises ValueError, mapped by
_loop_run to HTTP 400 with no side effects. No new route.
Honest limits are in the helper docstring: declared-identity
enforcement only (task rows store no authoritative executor /
coordinator / QA identity; task executor is a backend, not a
person), and audit-fix reaudit verdicts are out of scope
(the fix poster is not necessarily the re-auditor, so binding
signer to the fix poster would be a mis-binding).
(cherry picked from commit 56dce5165f4d08f38fe1730b9eea3b78f90cb97d)
Reconcile-port: handler_lifecycle.py docstring hunk redirected into cloud/server.py audit-verdict handler (target keeps 6-mixin split; enforcement itself is in audit_loop_wiring.py and applied cleanly).
Pre-fix any first-attempt audit failure raised NameError at
auth.py:424 (sqlite3 never imported), aborting the 5-attempt
retry loop on attempt 1 and masking the original error. The
424-line coverage mystery is also explained: the only full-suite
hit is test_child_model.test_coordinator_inherits_child_defaults
via worker best-effort _audit swallowing an OperationalError
(database is locked); 422-423/426-427 stay uncovered because
rollback succeeds and NameError aborts before either raise.
Bubble audit (see report): no merge/release gate swallows the
NameError and proceeds, so this stays a downgrade, not a
production blocker.
(cherry picked from commit 039c6adbac3b120b5b9a1dcbafeff6a91407ce70)
Postgres audit_ledger append-only guard gains exactly one
execution entry: cloud/ledger_pg_guard.apply_pg_guard, runnable
as 'python3 -m cloud.ledger_pg_guard' (explicit ops command,
not a migration script). It executes PG_GUARD_SQL verbatim from
the constant -- no second copy of the SQL anywhere -- refuses
non-PgConnection targets, does not commit itself (caller/CLI
commits), and refuses with exit 2 on a non-postgres backend so
a no-PG environment can never claim hardening. ensure_schema
still skips PG triggers; POSTGRES_SCHEMA still builds only the
table. Tests lock all of this with a recording fake connection
(no PG execution is faked), a source scan asserting only
ledger.py and ledger_pg_guard.py reference the constant, and
CLI dry-run/refusal cases. Real-PG syntax and UPDATE/DELETE
blocking verification is explicitly left to B5, not claimed here.
(cherry picked from commit b1f6c9158d628186965a9e57576ff8c9500e3f73)
Server POST /tasks/{id}/merge no longer has record/enforce/off:
hard_mode_from_env and HARD_MODES deleted (not re-defaulted),
handler always evaluates the hard gate, env CLOUD_MERGE_GATE_MODE
and body merge_gate_mode are ignored server-side. The record
'no-evidence passes' and 'unprotected+confirm passes with
merge_gate_risk' server paths are gone with the dead risk audit;
no production caller justified keeping them (only production
caller of the non-hard evaluator was the merge handler itself;
audit_loop_wiring already used the hard gate). cloud/merge_gate
pure functions keep their #209 semantics but are documented as
non-server: they are no longer wired to any endpoint.
Server-level record/enforce/off tests rewritten to the new
behavior (renamed, not deleted silently); pure-function tests
unchanged.
(cherry picked from commit 08e75edbd6d04701965c5db951fa054cfbb75bb0)
Reconcile-port: handler_lifecycle.py hunks redirected into cloud/server.py _merge_task (target keeps 6-mixin split; mixin file does not exist here); handler_support.py kept at HEAD (its A1 hunk is a docstring of _validate_hard_gate_body, ported to server.py copy); test mock paths cloud.handler_lifecycle.* rewritten to cloud.server.*.
- restore _audit_row in cloud/automations.py as a thin wrapper over
cloud.auth.audit_on_conn (legacy row + ledger entry pair,
user_id system, best-effort self-commit preserved). The stack's
gap-4 option A deleted the direct writer; main #275 resident.py
imports _audit_row, so the bare rebase broke server import.
No direct audit_log INSERT is reintroduced, so the #250
no-bypass scan stays green.
- conformance accounting 238/144 -> 243/149: main #275 added 5
resident methods that legitimately stay in server.py; moved
count stays 94, no duplicates.
- directional: resident x3, audit chain, conformance, automations
x3 suites, 51 tests OK
- whitelist expansion (tr + exit builtin) confirmed, comment only
- B4 viewport wording: temp banner removed, wording untouched,
change-list table stays in draft, not merged into body
- B3-tail (a)/(b): no content change (bullet still absent), recorded
as decided; v2 record showed it unselected, no side picked here
- section 4: 6 templates/artifacts/ refs deleted per signed verdict;
tripwire test updated 6 -> 0 to pin the new inventory
- CHANGELOG pointer line kept, no marker present
- workers audit landing migration confirmed, comment only
- verify-script gate evidence confirmed, gate hard untouched
- 2 temp stances NOT touched: actual-only quota, evidence binding
not enforced / no anti-restamp
- behavior code unchanged; directional skill tests 39/39 OK
- cloud/evidence_binding.py: bind/validate/nonce single-use store;
release endpoint consumes nonce on first validated submission,
replay -> 409 evidence_replayed, stale/future -> 409
evidence_stale, partial -> 400, absent -> old path + label.
Binding blocks exact replay and stale evidence, NOT restamping
(unsigned evidence cannot prove content freshness).
- cloud/quota.py docstring: actual-only is the v2 integration
position (already converged in v1 base via fef09ae, NOT in
main); original PR semantics (#86 all-rows vs #90 actual-only)
still pending coordinator confirmation at merge time.
- cloud/ledger_onboarding.py: DDL checklist referencing
PG_DDL/PG_MARKER_DDL constants, read-only verify_ledger_ready
and ledger_setup_report; default stays OFF, PG DDL stays an
operator action (never claimed as executed).
- cloud/ledger_outbox.py + worker hook: ledger write failures
go to a JSONL outbox in addition to the log and can be
replayed; task outcome still unaffected (rule 7 kept),
audit fail-closed untouched -- asymmetry documented, not
silently changed.
- cloud/gate_verification.py: read-only verifier over an
injected reader (match/mismatch/skipped); live wiring in
merge/release is NOT possible on the current contract (no
repo/ref in body or task row, no guaranteed trusted token),
proven in the module header; no fake verification claimed.
- cloud/tests/test_v2_evidence.py: 18 new tests, all passing.
(cherry picked from commit bd85e33cd5e846fa6ee5d7e2440182dbd9c758b0)
- db: translate INSERT OR IGNORE ... SELECT to ON CONFLICT DO NOTHING;
register task_audit_loop_state (task_id) in _OR_REPLACE_PK (direct port).
- server: _backfill_default_quotas uses SAVEPOINT containment, no longer
commits the caller's transaction (direct port; callers are init_db only).
- worker: NOT ported — integration _coord_child_rows has no column-fallback
retry loop, so the InFailedSqlTransaction cascade site does not exist here.
- tests: rewritten for the integration base (no cloud.audit_loop /
_coord_audit_loop_merge here); real-PG cases skip without DSN/driver.
(cherry picked from commit 79e6843610fcd275af874e7b4afc57af7ffa579e)
9 failures from v1 baseline 02835f6 cleared with provisional resolutions only;
each option pair is annotated provisional in verification/code-to10-v2-failures-summary.md
and awaits user confirmation. No check deleted, no skip added, no bare-phrase
stuffing, default hard merge gate untouched. Known remainder: one pre-existing
date-dependent test_arch_converge failure (Oct-dated fixture vs Nov current
month), failing on pristine v1 too, left for the quota track.
Preparation only: no v0.3/v0.4/v0.5 text is changed, nothing is
claimed transplanted or deployed. Stacked on feat-audit-pushback
(#244, 39966bd).
- Appendix provenance 00..09: templates/roles/00-08 sources
(byte-identical to #217) plus 09-auditor extracted from #244's
appendix; check_appendix.py extended 00..08 -> 00..09.
SKILL.md sole change (1 line, format-only): 09 chapter heading
normalised to the strict source form, otherwise the #217
checker cannot recognise the chapter. check_appendix.py passes
on the real pack: 10 chapters match; --emit outputs 10 chapters.
- TRANSPLANT-PREP-244.md: per-paragraph transplant diffs for
#244's +459 lines into v0.3/v0.4/v0.5 with anchors, grades
(merge / merge+note / partial stop / full stop), chapter-number
mapping (v0.5: 11-auditor, colliding 09-ops noted), the
templates/artifacts delete-references decision with 6 sites and
replacements, and the draft-sync checklist (sync itself is
post-deployment only, not done here).
- tools/check_transplant.py: anchors / draft-apply /
check-transplanted modes; draft-apply on the real v0.3/v0.4/v0.5
texts passes check-transplanted (drafts in /tmp only, not
committed). Stopped paragraphs are listed, never silently
rewritten: v0.3 G7/full-auto sentences, v0.4 G7 sentences and
the tighten-3 pre-rebuild confirmation, v0.5 whole QA section
plus its checklist item and the confirmation paragraph.
- Tests: test_skill_appendix_provenance.py (00..09) and
test_skill_transplant_prep.py; 13 new tests pass.
Suites on this tree: cloud 1523 OK (skipped=4), top-level 105
OK, deploy 39 OK.
- cloud/attempt_hook.py (new): canonical policy -- one counted
dispatch = one row, is_defect always False from the worker;
human/first rounds and human breakpoint resumes count;
platform auto-resume reruns, coordinator fan-out, and
pre-execution gate failures do not; transient retries inside
one dispatch are not new attempts. Platform origin survives
via the task_attempt_origin_markers table (same-transaction
write on requeue, queued_at generation guard on consume).
Defects are explicit-recorder only; corrections are
append-only (record_correction, last-wins).
- cloud/worker.py: Worker(attempt_ledger_hook=False) + CLI
--attempt-ledger-hook (default OFF = gap-3 behaviour exactly);
marker write in auto_resume_interrupted, marker consume +
classify in run_one, one ledger write at each terminal point
(cancel branch, normal terminal block); all hook writes
best-effort, never fail the task or block a requeue.
- cloud/attempt_ledger.py: corrects_attempt_no column (new DDL
supersedes gap 3's pre-merge DDL) + record_correction +
correction-aware aggregation; read path uses SELECT * so
gap-3-era tables still read; cost code untouched.
- cloud/tests/test_attempt_hook.py: 32 tests (policy, markers,
corrections, worker integration, mixed endpoint).
- docs/arch-conformance-metrics.md: policy, switch, PG manual
steps (new PG_DDL + PG_MARKER_DDL), remaining user decisions.
- host.py: 7 one-shot reads switched to with open (run_health_check
pidfile, daemon-status pid/log, daemon-stop pid, daemon old pid,
interrupt poll state, parallel jobs); read content, encoding args
and error semantics unchanged.
- tests/test_host_thread_cli.py: test_old_archive_unchanged state
read switched to with open; added a warnings-capture assertion
scoped to the state read so a regression fails the test.
- Intentionally untouched: daemonize log handle (daemon-lifetime
dup2 target) and the two _load_config_file / init sites owned
by PR #266.
Cycle tasks never reach topo sort: ready_tasks filters by
'deps all completed' first, so they neither raise ValueError nor
zero out other ready tasks (5 ready + 1 cycle pair still counts 5;
only a pure cycle counts 0, because its ready set is empty).
Counting logic unchanged.
- cloud/audit_scorecard.py: pure stdlib scorer, structured evidence in,
7 dimension scores (0-10, 1 decimal), basis, missing/untested items,
overall pass only when all 7 >= 9; unknown -> None, never 0/10/estimated;
fixture/viewport/code-review evidence -> untested, never pass;
thresholds all test_default_unapproved, user_approved only on explicit input.
- server.py: GET /audit-scorecard read-only, same auth/member scope as
/metrics, no DB writes, no ledger writes, no pushback/reaudit triggers;
on main base it honestly reports unknown/not-enforced, overall not_pass.
- docs/audit-scorecard.md: spec, formulas, threshold table, unknown
handling, overall rule, 10-to-7 merge mapping, relations to
#238 metrics-report, #244 sampling, #260/#261 loop, and a system-computed
example (current state, overall not_pass) clearly separated from the
previous judgement estimates.
- tests: 12 new items pin all-pass, one-below-nine fails, average 9.1
with one 4 still fails, unknown None, fixture untested, conditional
not a pass, thresholds unapproved, endpoint 401/200 read-only.
- suites: cloud 1442 OK (skipped 4), top-level 105 OK, deploy 39 OK.
Server-side execution for the track-1 pure driver (#260), no driver
semantics changed: audit verdict / fix completion / correction /
rebuild confirmation endpoints persist LoopState in audit_loop_state,
write every step through the #241/#247 ledger path (ledger first,
state second, fail-closed), derive fix/rebuild subtasks, and block
merge/release via the existing #247 chain reader. POST
/tasks/{id}/release follows the #259 contract (global-admin write,
fresh hard-gate evidence, merged status, multi-source 409 with no
audit write on block); deploy.py stays outside task gates.
LoopState + decide_audit + after_fix + apply_correction +
confirm_rebuild + emit, all pure: the driver returns Action
objects, the caller executes (subtask creation, merge/release
blocking, ledger persistence). Counts per (module, contract
version); pushbacks 1-3, 4th failure (> 3) rebuilds, 1 rebuild
and 1 targeted round per version, third-failure triage
(structural -> early rebuild, contract/design -> refreeze,
near-complete -> targeted), rebuild-only-implementation with
the old-test first gate, acceptance rebuilds pending user
confirmation, quota blocks consume nothing, cost threshold
escalates rebuilds to the user with full history, corrections
to pass do not unblock, ledger events match the #241 table
with CONDITIONAL mapped to fail at the boundary.
No server wiring, no release endpoint, no task creation, no
DB access in this track; wiring is the next track's job
against this interface.
Single sampling rule; track 3 (#244) is authoritative for WHO is
mandatory, track 5 (#238) for HOW the rest are drawn:
- Mandatory (1.0): golden-task records + rebuild / refreeze /
escalation records + third-failure near-complete still-failed
cases (mandatory_triggers, full list per case). Track 3's old
deterministic every-10th ordinary rule is removed (it drew zero
from batches under 10 ordinary records and was predictable);
track 5's old narrow mandatory list is expanded with golden /
refreeze / escalate. blocked_quota stays non-mandatory.
- Random tiers: normal 0.10, critical-path 0.50, seed required
and recorded, ceil so a non-empty group is never rounded to
zero. 0.10/0.50 are TEST DEFAULTS, not approved production
values; production rates remain a user decision.
- One pass, no double counting: mandatory records leave the
random pools before drawing; a multi-trigger record (e.g.
golden + rebuild) is sampled once with all triggers listed,
and its correction follows the (original, corrected) verdict
pair, never the trigger list.
- cloud/audit_pushback.py::select_audit_sample is now only a
record-shaped adapter over cloud/audit_sampling.py::select_sample
(seed required, retired reason names critical_path-for-kinds
and periodic_sample removed); AUDIT_SAMPLE_EVERY_NTH deleted.
- Correction: original never mutated, correction appended,
corrector != signer enforced in audit_sampling when both names
are supplied; gate effect pinned (correction to pass never
unblocks by itself -- schedule/cancel-and-rerun, blocked until
a new passing re-audit; correction to fail re-blocks at once).
Track 3 / track 5 retrigger vocabularies documented as synonyms.
- Skill text (tightening 3 item 2 + correction paragraph)
rewritten to the unified rule; new test file pins the
boundaries (mandatory union, dedup, ceil incl. the 9-record
case, seed reproducibility, golden-set construction under both
sample vocabularies, adapter order/non-mutation, correction
preservation on both tracks).
Bypass closed: automations._audit_row (direct audit_log INSERT,
best-effort swallow) is deleted. run_due_automation now writes via
cloud.auth.audit_on_conn, the same audit_log + audit_ledger pair
AuthStore.audit() writes, sharing created_at. The audit pair runs
before record_automation_run's commit, so task row, run row and
audit pair commit or roll back together; chain-head IntegrityError
retries the whole block up to 5 times, other errors roll back and
raise (fail-closed). The claim stays committed, so a failed run is
retried next interval, never recorded as a failed run.
AuthStore.audit() now delegates to audit_on_conn; its pair, retry
count and fail-closed semantics are unchanged.
- cloud/attempt_ledger.py: separate task_attempts table (not tasks
columns) with per-attempt rows, MAX+1 attempt_no, append-only
intent; interrupted rows kept but never counted; is_defect is
explicit, never inferred from failure; first defect category
wins; no-ledger tasks stay unknown (None), never backfilled.
SQLite DDL is CREATE TABLE IF NOT EXISTS (idempotent); PG_DDL
is the verbatim manual step for production Postgres, kept out
of POSTGRES_SCHEMA on purpose (migrations coverage untouched).
- cloud/server.py GET /metrics-report: aggregates the ledger
into compute_metrics records; missing ledger table degrades to
unknown rates, not an error. Cost dual-column code untouched.
- cloud/metrics.py: docstring only (ledger is now the source);
compute_metrics logic and all #238 semantics unchanged.
- Tests: 16 ledger tests (store/aggregate/metrics/PG adapter
evidence) + 3 endpoint tests (real rates, old task beside
ledger task, interrupted status non-terminal).
The #241 import resolution deleted the whole 'from cloud.cost import
CostStore, dual_column_view' line to drop the unused alias, which
also removed the CostStore import that _quota_snapshot needs; every
task creation then failed closed with 503 quota_check_unavailable
(4 EndpointChainTestCase failures in test_audit_wiring). Restored
as a plain CostStore import; dual_column_view stays available in
cloud/cost.py for #241's own tests.
- cloud/audit_sampling.py (pure, stdlib): mandatory sampling (1.0)
for rebuild cases and for third-failure near-complete cases whose
continuation round still failed; random sampling for the rest
(normal 0.10, critical-path 0.50, ceil so small groups are never
rounded to zero, seed required and recorded for reproducibility).
- Wrong-verdict handling: correct the original conclusion and
retrigger the corresponding flow (wrong pushback ->
cancel_pushback_and_rerun_audit; wrong rebuild ->
halt_rebuild_and_restore; wrong pass -> reopen_audit_and_pushback;
unknown original -> escalate_to_human); every result, correct or
corrected, is appended to the audit-of-audit record.
- Golden-set approval checklist: list reviewed, criteria defined,
sample exclusion, approver+timestamp -- all human-confirmed before
approval; sample entries (sample=True or sample- ids) are dropped
and counted, never counted as golden.
- metrics.build_report gains an optional sampling_plan section;
without a plan it states sampling is pending, never implying the
audit was re-examined.
- cloud/arch_conformance.py: contract-version (+1 only, approved
change record), forbidden-directory (own dir only, .. and symlink
escapes resolved), forbidden-import layer bans audited against the
baseline (foundation/execution/driver, server sqlite3 Row adapters
deliberately allowed), G2 frozen-stack swap guard; skill-project
one-call check.
- cloud/metrics.py: first-pass/defect/rework rates over terminal
tasks, unknown rates as None (never 0), #212 dual-column per-task
cost (total None when incomplete), pure feedback rules
(tighten/route-with-extra-audit/recalibrate, auto_apply False),
golden-set gate: unapproved sample set is never decision-ready.
- GET /metrics-report (authenticated, member-scoped, same as
/metrics): attempts/defects are None until an attempt ledger
exists, so rates stay unknown rather than fabricated.
- Tests: 29 arch + 15 metrics + 3 endpoint, each check with
positive and negative cases; repo-level scan pins zero layer
violations on the baseline so a future violation fails CI via the
existing host-verify discover run (no workflow change).
Track 3 decisions (audit_pushback) now really block the server merge
endpoint and really land on the track 4 hash chain, via the new
cloud/audit_wiring.py translation layer:
- POST /tasks/{id}/merge ANDs the chain pushback block
(merge_blocked/release_blocked/blocked_by=audit_pushback/round/
failed_checks, replayed per module+contract version) with the
hard gate's five checks in every gate mode; either side blocks
-> 409, status unchanged, audit_pushback_blocked appended to
merge_gate.blocks. Ledger reads that fail (other than a missing
audit_ledger table) fail closed to 409.
- Every pushback/fix/re-audit/rebuild/escalation step is written
to the ledger with track 3 fields authoritative and track 4
storage authoritative; CONDITIONAL maps to fail at the boundary
with the original preserved, history never rewritten.
- Hard-gate 409 blocks codes feed pushback packages machine-first
(branch protection survives as its own extra check).
- Re-audit QA guard: a claimed pass with un-rerun or failed QA
links never reaches the chain.
- Track 4 compat: one audit_wiring_event action for escalation /
blocked-quota / audit-sample kinds, counted in the timeline.
- Resolved tension (documented in audit_wiring.py): a meta-review
correction to fail re-blocks at once, but a correction to pass
does NOT unblock by itself -- track 3 schedules a re-audit,
it does not release the gate; track 4 failure counts still use
effective verdicts, so the two views may disagree while a
re-audit is pending.
Tests: cloud/tests/test_audit_wiring.py (18 items), incl. a real
HTTP end-to-end chain (fail -> 409 -> fix -> re-audit pass ->
200) and a separate QA-link chain test.
Independent audit (no code, independent re-run, separate signature;
CONDITIONAL never passes; deviation needs due todo) and automatic
pushback (fail -> derived fix subtask, merge/release blocked,
package with per-item evidence, full re-audit, 3-round limit with
a separate CI auto-fix counter, ledger hash-chain interface for
track 4, gate-block interface for track 1). Over 3 failures per
module per contract version: rebuild only that module from the
frozen contract (old implementation archived, avoidance list),
contract/design root cause -> G2 re-freeze escalation instead.
Tightenings: 3rd-failure triage (structural early rebuild /
early refreeze, near-complete one targeted round), rebuild keeps
tests and passed assets with an old-test first gate, audit-of-
audit sampling/correction and G6 user-only acceptance plus
acceptance-rebuild user confirmation. QA full chain: self-test
three parts, PRD coverage, agent six, cross-client matrix with
honest uncompiled verdicts, G5 gates, impact-scoped regression,
dual QA/audit signatures, failed-link re-runs in re-audit.
Skill text is purely additive (+459/-0) against main v0.2 text;
port this section into v0.3/v0.4/v0.5 texts on merge, never
overwrite them with this file.
Integration arbitration, not a PR commit: v0.3/v0.4/v0.5 synced body
changes into appendix chapters 00/02/04/06/07 while templates/roles/
sources stayed at v0.2, so check_appendix failed 6 chapters; and
09-ops/10-iteration chapters (v0.5) were invisible to the strict
chapter regex because of the ', v0.5 new' title suffix, breaking
the #214 matrix-vs-appendix test. Direction is sources follow the
appendix (appendix is the tenant-visible authoritative version;
never overwrite v0.5 text with v0.2). 09/10 headers normalized to
strict form (suffix only); 09/10 sources split mechanically from
the appendix; EXPECTED_IDS extended to 11 chapters in the checker
and both appendix tests.
- test_skill_appendix_sync: all 9 appendix chapters 00..08 in order,
body role-matrix set == appendix role-title set, authority
statement on both sides, GitHub guarantee in body + named
appendix chapters 00/04/07, seeded copy byte-identical with
full appendix. Previously only 00/08 existence was asserted.
- test_skill_seed_pinning: only SKILL.md copied (pack extras never
land, never resurrected), tenant extras left alone, tenant-modified
SKILL.md never overwritten across repeats, identical = noop,
deleted SKILL.md re-seeded (documented boundary, not deletion
preservation).
- skill_seed.py: evaluation note only (why whole-dir copy is not
done); no code change.
- CostStore: per-model breakdown, report_by_creator (JOIN tasks.created_by),
dual-column reports with priced_usd_micros, unpriced_models listed
separately and a billing note (proxy price, not the relay bill);
_report itself untouched, old exact-key tests keep passing.
- Hard cutoff: cost.quota_cutoff_reason is the single USD/unpriced-cap
decision; worker._quota_exceeded now delegates to it (identical
messages); claim gate, turn boundary and poll kill stop spend and
record interrupted + quota reason + audit; auto-resume re-checks
live quota before requeueing.
- Approval timeout (G7 ops metric): cloud/approval_timeout.py —
default stays 600s, >20% timeout rate recommends 1800s, <5% keeps,
mid-range keeps; stats + recommendation + info log; wired into
/metrics (member projects, no log spam) and a global-admin
/admin/ops/approval-timeout endpoint (sweeps overdue first).
- Server: task/project cost endpoints return dual-column reports;
new GET /users/me/cost and GET /admin/cost-by-creator.
- Tests: cloud/tests/test_cost_quota.py, 10 tests.
Core artifacts travel as an explicit manifest; coordinator triple-checks
(core report vs manifest vs recomputed hash) before cleanup, regression
rewrites files verbatim, recomputes sha256, stops on ARTIFACT-HASH-MISMATCH
and only then runs the same core code plus import-only regression tests.
GET /full-auto/status, GET /full-auto/config-plan, POST
/full-auto/validate-action|merge-gate|ci-fix|release-plan|
rollback-plan, POST /full-auto/execute (global admin only,
FULL_AUTO_ENABLED + verified installation + branch protection
verified via GitHub API, else 403 + audit), GET /full-auto/audit.
Guards reused from cloud/full_auto.py; token only in Authorization
header; audit via audit_record redaction into AuthStore audit_log.
Only pure computation/validation sub-blocks of _handle_github_webhook,
_create_followup, _deliver_to_thread and _task_chat_history are extracted;
all DB writes, audits, external calls and side-effect blocks stay in the
callers, error strings/codes and response shapes verbatim. _deliver_to_thread
uses the shared goal classifier only for /goal input: its non-goal terminal
path still goes to CAS revive (not 409) and its merged path keeps its own
wording. New tests: test_longfunc_remaining.py (15).
_continue_task 226->168 via _validate_continue_options;
_task_wait 158->139 via _validate_wait_params;
_task_thread_read 187->152 via _summarize_trace_entries;
_metrics 123->104 via _summarize_metrics_tasks.
No behaviour, message, status or route change; error strings verbatim.
New tests: test_longfunc_extra (9). Cloud full suite: 1439 OK, skipped=4.
Follows #229 pattern; must merge after #229 and be rebased/checked then.
Move the /tasks/* sub-route out of _dispatch into _dispatch_task_route
(query passed explicitly; the files/tree/thread branches use it), move
the push-subscription DELETE branch out of _dispatch_project into
_project_push_subscription_delete, and turn the goal-validation block
of _create_task into the pure module function
_validate_task_goal_options (returns error string instead of sending
400, caller sends it unchanged). Statements are moved verbatim except
indentation and the goal send+return -> return-error transform; no
behaviour change. server.py module split intentionally not done: it
remains a larger, higher-risk refactor.
Add test_longfunc_split covering the pure goal validator's success
paths and all eight error strings.
- ensure_workers_table: new tables include host TEXT, pid BIGINT,
address TEXT; old tables gain them via _ensure_hostpid_columns
(PRAGMA column check, missing-only ALTER, old rows keep NULL,
never fabricates host/pid). SQLite and Postgres share this path
(PG via the db adapter's PRAGMA translation); POSTGRES_SCHEMA
deliberately still excludes workers, which is volatile heartbeat
data in UNMIGRATED_TABLES (adding it broke the P2 migration
alignment tests, verified and reverted).
- heartbeat/register: given host/pid/address refresh, omitted
values preserve the old row; address has no derived source.
Local worker passes host_name + own pid; HTTP heartbeat only
stores caller-supplied values, never the server's own.
- classify_stale_workers: kill only when age strictly exceeds the
threshold AND host matches local host AND pid is proven dead;
equal threshold, foreign host, missing pid, or live/unknown pid
are list_only. Classification only, never kills, never matches
by name/prefix, never touches infra containers.
SKILL.md header pointed at same-dir README.md but the pack had no
such file (CHANGELOG was supplied by #217, README was not). This
track adds the README only: purpose, chapter map, appendix
provenance entry point, seeding behaviour, and known boundaries.
It states no rule, changes no SKILL.md semantics, and does not
duplicate the CHANGELOG/script explanations -- overlapping parts
are pointers, with an explicit division-of-labour section.
Deletion retention is written only as the tombstone-design
placeholder, deferring to the skill_seed implementation, because
no tombstone implementation has landed. Test pins that every
file SKILL.md cites as same-directory exists (CHANGELOG, README).
SKILL.md claimed its appendix was spliced verbatim from
templates/roles/ by a generation script (see CHANGELOG), but the pack
contained none of the three. Add the nine role sources (mechanically
split from the v0.2 appendix; SKILL.md untouched, zero semantic
change), tools/check_appendix.py (verify + --emit, stdlib only, never
edits SKILL.md), CHANGELOG.md, and test_skill_appendix_provenance.py
(5 tests). Seeding unchanged: skill_seed copies SKILL.md only, so
templates/tools/CHANGELOG never reach the tenant home.
- cloud/merge_gate_hard.py (new): pure hard gate; mode resolution is
env-only (unset/invalid -> hard, fail-closed); five checks plus
branch protection must be protected; unknown/unprotected block
even with explicit confirm; no-evidence blocks (fail-closed);
directory check reuses #209 check_directory_isolation (v0.3 rule);
blocks codes + merge_gate_blocked audit detail are the track-3
pushback interface (no pushback state machine in this track).
- cloud/server.py: POST /tasks/{id}/merge uses hard mode by default;
record/enforce/off only when explicitly set via env (preserving
#209 semantics); body merge_gate_mode is ignored server-side so
callers cannot downgrade the gate; every gate block writes a
merge_gate_blocked audit before returning 409, status unchanged;
hard-mode body type errors return 400.
- docs/merge-gate-branch-protection.md (new): protection payload
(PR + strict status checks + no force-push + no deletion +
enforce_admins), empty required_checks = unconfigured, Pro/public
prerequisites, 403 causes; states protection is NOT claimed enabled.
- SKILL.md (gate-mechanism wording only): four checks -> five checks;
body + coordinator + client appendix gate paragraphs updated to
default hard, merge_gate_blocked audit, payload doc reference.
- tests: 31 new hard-gate tests (pure + endpoint + skill text);
#209 endpoint tests now set record env explicitly (their intent);
test_server_merge 200-path calls now carry full hard evidence.
- cloud/merge_gate.py: pure gate; directory check reuses the v0.3
git diff --name-only merge-base command; protection modes
record (default, explicit confirm required when unprotected),
enforce (always blocks when unprotected), off (explicit only).
Confirm covers protection risk only, never directory violations.
Protection status is caller/env-declared only; unknown is a
recorded risk, not a claim of protection.
- server POST /tasks/{id}/merge: applies the gate only when gate
evidence is supplied (or env declares protection); no-evidence
calls behave as before plus a merge_gate field in the response.
Blocked merges return 409 without changing status; confirmed
unprotected merges write a merge_gate_risk audit entry.
- skill_seed.py: whole-directory seeding evaluated and rejected
(cannot preserve tenant deletions; no benefit while the pack
holds only SKILL.md); SKILL.md-only behaviour unchanged.
- software-engineering SKILL.md: gate paragraph added to the body
and to the coordinator + client appendix sections (v0.2 text on
this base; port to v0.3 text on rebase after #199 merges).
- tests: 16 new tests covering gate modes, directory reuse,
body/appendix bidirectional assertions naming the missing side,
SKILL.md-only seeding and tenant-edit preservation.
Stacked on v0.3 + v0.4 (HEAD d6522ba feat-full-auto): v0.3/v0.4
content preserved, not reverted. Adds G7 post-release ops and
observation (monitoring, errors, cost/quota, feedback, hotfix loop,
rollback triggers, ops manual) and G8 iteration and scaling (v1.1
handover list, contract v1->v2 protocol, new-end onboarding,
scale/performance, deprecation), with G6->G7 and G7->G8 gates,
DAG, gate table, coordinator derivation, G0-G8 todos, checklist,
09-ops and 10-iteration role templates, and ops-manual / v1.1
handover templates in both body and appendix. Settles web+React+
multi-viewport focus, cross-viewport E2E, web single-end release
with iOS/Android native postposed, open-source-first, and the
TypeScript full-stack default (user-request-only language change;
codex-app-server itself stays Python). Pending #199 and #205
merge, then one rebase round. PR only, do not merge or deploy.
- P1-1 directory-isolation executable check: git diff --name-only
against merge-base, output recorded in merge record
- P1-2 derivation checklist table (8 required items, missing one blocks dispatch)
- P1-3 QA split into single-client review and cross-client E2E with
fixed deliverables and agent-test attribution
- P1-4 sub-task slim-pack trimming rules (listed chapters only, never full copies)
- P2: inter-thread Q&A default policy, docs/decisions.md append-only,
per-client onboarding docs (web first at G4a), v1.1 handover checklist
- Body and appendix kept in sync; new lightweight keyword test
- worker._tenant_dirs: playwright refresh log used undefined 'task';
use the method argument project_id (ruff F821, real bug: NameError
aborted tenant seeding when a playwright script was refreshed).
Regression: cloud/tests/test_worker_playwright_seed_f821.py (2 tests;
pre-fix run reproduces NameError at worker.py:2780).
- harness2.assign_ids: hashlib.sha1(..., usedforsecurity=False) -- the
digest is only a deterministic shuffle seed, not a security use;
digest value unchanged, bandit B324 no longer fires (High 5 -> 4).
- harness2.select_final: rename ambiguous loop var 'l' to 'lab'
(ruff E741, behaviour-invariant).
- Deliberately NOT changed: host.py shell=True x4 (see PR body -- all
four are intentional shell commands, changing semantics is unsafe),
remaining ruff 109 / mypy 24, workflow thresholds (75% coverage
gate unchanged; no gate was relaxed or removed).
Phase 1 of audit-hardening CI expansion (baseline origin/main aebc623):
- lint: ruff E9 hard gate; full E,F@120 report-only (baseline 111 items)
- typecheck: mypy default mode, production scope, report-only (baseline 24)
- security: bandit production scope, report-only (baseline 287, High 5)
- depscan: pip-audit on requirements-ci.txt, hard gate (0 vulns; repo has
no production dependency manifest, see summary)
- coverage: production 58 files, fail-under=75 (baseline measured 76%)
- release build: E9 + pip-audit hard gates before packaging; full reports
stay in host-verify to avoid drift
No production code changed; CI billing failure mode unchanged.
Long-running website monitoring: new cloud/resident.py layer on top of
the automations claim/quota model (no task state-machine change).
States running/paused/ended; pause preserves the content-hash cursor
and stops polling, resume re-checks from the cursor, end is terminal
(resume after end is 409, a new watch is required). Polling is a cheap
fetch + hash comparison; only a real change creates a queued model
task. Quota exhaustion pauses (quota_exceeded) instead of ending and
keeps the cursor. Driver is the worker run_loop round boundary;
server exposes create/list/get/update/pause/resume/end with audit.
Console UI intentionally not wired this round (see verification
summary for deviations).
- search(..., allowed_hosts=[...]) extends (never replaces) the 15-domain
code list for that call only; scope=general + allowed_hosts raises,
empty/illegal values raise before any HTTP, dot-boundary matching,
extension values never in error strings or attempted entries.
- Native-filter parity: Tavily/Exa get the effective list, Perplexity
skips its native filter past its 20-domain cap (post-filter stays
authoritative), Brave/SearXNG/DuckDuckGo rely on post-filtering.
- MCP web_search gains allowed_hosts passthrough (only when supplied,
so older search_fn fakes keep working).
- JS-heavy evaluation documented in docs/web-search-mcp.md, mock-first:
direct fetch is shell-only by construction; provider extract can
supply rendered text in mock only; live SearXNG in this VM env
failed (remote closed connection) and paid-backend live effects are
unvalidated blockers, not claimed.
- Tests: test_code_search_ext (9) + test_js_heavy_extract (5 + 1 live
skipped); existing websearch/code-search suites still green.
(cherry picked from commit 9c26bbe848438e1f34bcb000700aeb4c284f4f23)
- click/click_ref: pre-click allowlist gate on resolvable anchor href
(same check as navigate; miss refuses before the click executes);
click/click_ref/click_at: post-click landing URL check.
click_at has no pre-click gate by design (coordinates name no
element); JS-driven navigation, evaluate, meta refresh and popups
are documented as unintercepable with the Tinyproxy 403 backstop.
- Tenant script upgrade is now safe: only proven-unmodified scripts
(manifest match, 2a569ac known-shipped SHA-256, missing, or
current) auto-merge; tenant-modified scripts are never overwritten,
are reported as skipped, and get one deduped audit record. The
manifest never learns a tenant hash (else the next upgrade would
mistake a modified file for untouched). Idempotent reruns.
- Activation candidate = deployed Tinyproxy programming set encoded
with leading-dot entries (plain entries would regress
docs.python.org); npmjs.org per the deployed filter, not npmjs.com.
Host activation itself is NOT performed this round: only the plan,
validation tests and rollback are delivered.
Tests: 28 new (click 13, upgrade 8, activation 7), all fake/no-network;
98 browser-related tests pass including prior suites.
(cherry picked from commit e4a93f5748ec815f163d0bc096ff4dc4b8c651f5)
- Multi-tab: browser_new_page / browser_list_pages /
browser_switch_page / browser_close_page in playwright_mcp.py;
each new page gets a fresh Playwright context, so cookies and
storage are isolated between pages. Single-page tools act on
the current page; switching to a missing index is an error,
not a crash; closing the last page is an error (use
browser_close).
- Navigation allowlist: BROWSER_ALLOWED_HOSTS (comma list,
leading-dot suffix match, case/port-insensitive) checked in
_check_allowed_url before launching Chromium; unset (or empty)
means no check, the old behaviour. file:// always allowed;
localhost/127.0.0.1 have no special treatment.
- Close cleanup: browser_close closes all pages and best-effort
removes cwd browser-live.png / browser-live.json; custom
screenshot files are untouched. list_pages needs no launch.
- Egress filter: 10 programming-source Tinyproxy BASIC rules
(python.org, nodejs.org, go.dev, pkg.go.dev, rust-lang.org,
developer.mozilla.org, crates.io, stackoverflow.com,
cve.mitre.org, nvd.nist.gov); already-present github.com /
pypi.org / npmjs.org not duplicated; searx.diante.me stays
exact-only, no diante.me wildcard.
- Allowlist worker wiring: _docker_task_context now passes a
set, non-empty BROWSER_ALLOWED_HOSTS from the worker env into
[mcp_servers.playwright.env] in the tenant config, so the MCP
process actually sees it (container -e alone does not reach
MCP subprocesses, 2026-10-30). Unset/empty writes nothing and
never overwrites; the value never goes into logs; it is not
added to the websearch env block or the container env.
ensure_playwright_mcp_env now returns False without rewriting
when the result would be identical (canonical join: one blank
line before the block), so a second prep leaves an unchanged
tenant config alone; other sections untouched.
- Skill + docs: browser-automation SKILL.md multi-tab/allowlist
section; docs/platform-browser.md comparison doc (new file).
- Tests (mock/fake only, no network, no real Chromium, no real
credentials): cloud.tests.test_playwright +
cloud.tests.test_websearch_worker, 48 tests OK, including 6
new wiring tests (switch off passes no variable, switch on
reaches MCP env verbatim, empty never written, old-tenant
merge idempotent + no-rewrite, ensure no-op when identical,
allowlist enforces only after wiring).
- NOT done (see PR body): no persistent profile, no credential
vault, no approval card, no CAPTCHA bypass, in-page link
navigation does not pass the allowlist, no real-Chromium live
verification, host tinyproxy filter not reloaded.
- search(..., scope=code) restricts to 15 programming domains
(official docs, GitHub, registries, Stack Overflow, CVE/NVD):
native include_domains/search_domain_filter where supported
(Tavily, Exa, Perplexity), post-filtering for all providers
(Brave, SearXNG, DuckDuckGo have no native filter).
- web_fetch now uses fetch_best: Tavily extract, then Exa
contents, then direct fetch; both-fail errors name both
failures, direct-fetch 403 under the egress whitelist is
fail-closed, not bypassed. Default general scope and direct
fetch_url behaviour unchanged.
- MCP web_search gains scope enum (general/code); docs updated.
- Tests all mock HTTP, no real keys.
Deploy restarts kill running tasks: the worker marks them
interrupted (worker_shutdown on graceful exit, sweep on stale
reclaim), and auto_resume_interrupted used to accept goal tasks
only, so every non-goal task killed by a deploy stayed
interrupted forever unless a human retried it (the 2026-10-30
demo task died this way).
Now worker_shutdown/sweep interruptions are re-queued for
non-goal tasks too: the platform killed the task, so the
platform re-queues it. The re-queue is a fresh run, not a
breakpoint resume: goal_resume_pending is forced to 0 and any
stale resume_message is cleared, so the new run cannot be
hijacked by the goal-resume path or an old continue message;
files in the same worktree persist. All existing guards stay:
user pause, coordinator orphans, quota (still goal-only),
backoff, the 3-strike stall guard (non-goal tasks have no
checkpoints, so each platform re-queue counts as no-progress),
and the auto_resume_max cap. Other non-goal reasons (NULL,
quota, user cancel) still do not auto-resume.
- AutomationPanel rows now show title, prompt content (160-char preview with expand/collapse), repo_url, last/next run, run_once badge and cancelled time.
- Active automations get an inline edit form (name/prompt/interval/repo) that PATCHes only changed fields via the existing automation_update endpoint; backend semantics unchanged (no last_run_at reset, cancelled stays terminal).
- Rebuilt cloud/console.html with tsc + vite (build green); automation python tests 18/18 OK.
Stale bridge state used to end the stream on the first stale read,
so every round gap and every worker restart killed the watch (the
2026-10-30 demo watch died this way when a 15:03 deploy restarted
the worker). Now a stale state switches to a waiting heartbeat
every 5s while the task is still alive (running/queued/paused/
interrupted), and frames resume when the next round's MCP starts
polling. The stream ends only on a terminal task state, a missing
task row, or 10 minutes of continuous staleness. The panel shows
a waiting status and keeps the last frame. Also force-close the
SSE connection on stream end: HTTP/1.1 keep-alive otherwise left
terminal streams hanging with no EOF.
- cloud/tree.py: structured worktree tree (dirs first, ignore
.git/node_modules, symlinks not followed, entry/depth
truncation flags, text render for injection)
- GET /tasks/{id}/tree with /files auth + worktree resolution,
max_depth 1..5, max_entries 1..500, escape 400, missing 404,
cloud/cross-host 409
- MCP get_task_tree translating to the new endpoint
- worker first-round injection of a repo-checkout top-level
snapshot (2 layers, 120 entries) after repo resolution,
labelled as checkout snapshot not worktree; skipped for
coordinator/resume/fork, silent when no host source
- docs/project-tree.md; tests for tree, endpoint, injection,
MCP translation