Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4337560
docs(planning): burst-compute queue — T5500 down, T3.4 fired
Oct 27, 2026
941f0d6
feat(bench): jev27 contamination-audit arm + Modal burst (B2 staged)
Oct 28, 2026
98ec94f
feat(bench): d1-2B survey arm — JevBench-231 0.7489 via /v1/systemone
Oct 29, 2026
64c3094
feat(calib): per-kind T beats global 2x on E1-E board
Oct 29, 2026
cbcc68e
docs(oper): T3.4 session-3 record — cap kill at 4300, ckpt@4000 safe
Oct 29, 2026
2d68563
docs(calib): out-of-sample correction retracts per-kind claim
Oct 29, 2026
03796dd
feat(bench): trainer v6 partial manifest + T3.4 session-3 verify
Oct 29, 2026
1181496
docs(research): §33 jevgrep re-read + Tev1-4B audit; d1 teacher runner
Oct 29, 2026
1fd4b92
fix(bench): teacher pass survives transient server failures
Oct 29, 2026
a37e5cf
feat(bench): 4-worker concurrent teacher pass
Oct 29, 2026
8885696
docs(oper): record teacher-runner concurrency scope decision
Oct 29, 2026
9cc7977
docs(oper): d1 teacher-pass throughput finding — full stream host-blo…
Oct 29, 2026
fa1170d
docs(oper): GitForge CI trigger retry-storm diagnosis
Oct 29, 2026
e77105f
fix(engine): rename betacf bindings for clippy similar-names
Oct 29, 2026
c6b3b1a
fix(deps): pin internal config-crate path deps
Oct 30, 2026
03ed530
chore(ci): redrive stalled pipeline triggers for e77105f/c6b3b1a
Oct 30, 2026
e28f2b2
docs(oper): CI lane green on 03ed530 + lock-storm defects
Oct 30, 2026
70f8d6f
chore(ci): redrive stalled pipeline triggers (e77105f/c6b3b1a/03ed530…
Oct 30, 2026
b9c6160
chore(aegis): regenerate baseline for line shifts since 9cc08e0
Oct 30, 2026
3457818
test(engine): cover rotation combine and numeric edge branches
Oct 30, 2026
0840631
test(cli,engine): lift coverage floor over 97.5 in-image
Oct 31, 2026
6471ad7
docs(oper): CI triage runbook, in-image coverage rule, lane-fix debt
Oct 31, 2026
70735a8
chore(aegis): regenerate baseline for docs line shifts in 6471ad7
Oct 31, 2026
0d132ea
chore(aegis): regenerate baseline for AEGIS_AUDIT doc section shifts
Oct 31, 2026
e6a3624
chore: integrate PR #11 (prebuilt release installer) into main
Nov 1, 2026
f27e997
fix(cli): bound image reads, refuse lane overwrite, drop assert
Nov 1, 2026
b96771f
chore(aegis): regenerate baseline for .gitforge.yml line shifts
Nov 1, 2026
69c6869
style(cli): match CI rustfmt form in read_bounded
Nov 1, 2026
f724f94
chore(recipes): re-record expected captures after cache-key drift
Nov 1, 2026
6764c00
chore(aegis): regenerate baseline after recipe re-record
Nov 1, 2026
2cacf2d
docs(handoff): record green pipeline closure for 2026-11-01 session
Nov 1, 2026
74bf714
chore(release): 0.6.2 changelog with the shipped feature set
Nov 1, 2026
8b45cc1
chore: merge GitHub sync commits (PR #13/#14) into main
Nov 1, 2026
220472b
docs(planning): quality and coverage campaign plan 2026-11-01
Nov 1, 2026
c844a4b
chore(aegis): regenerate baseline for doc prose drift
Nov 2, 2026
990ebdc
chore(ci): retrigger pipeline for c844a4b after GitForge deploy
Nov 2, 2026
4706b7c
chore(ci): retrigger after scheduler restart recovery
Nov 2, 2026
5607cb6
chore(ci): clean retrigger
Nov 2, 2026
67d7b4a
docs(planning): GitForge nested-BEGIN fix + green pipeline receipts
Nov 2, 2026
8953845
chore(aegis): regenerate baseline for new planning doc prose
Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
754,736 changes: 1 addition & 754,735 deletions .aegis/baseline.json

Large diffs are not rendered by default.

67 changes: 63 additions & 4 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ project does not yet guarantee a stable public API — version `0.x`
releases may break, and every breaking change is recorded in this file
(and reflected in `docs/DECISIONS.md` where a binding decision moves).

## [0.6.2] — Unreleased
## [0.6.2] — 2026-11-01

The planned 0.6.2 release will package the current engine/CLI from a clean
tagged source tree and add a verified prebuilt installation path. It will
not include model weights in the binary release.
The 0.6.2 release packages the current engine/CLI from a clean
tagged source tree and adds a verified prebuilt installation path. It
does not include model weights in the binary release.

### Added

Expand All @@ -28,6 +28,65 @@ not include model weights in the binary release.
three affected WASM conformance fixtures are re-recorded with their
answers/confidence/outcomes unchanged.

### Engine and core

- Rotation readout — position-bias-corrected decision distributions with
a certified early exit (`33ff9c5`, `4d9e755`).
- Family tier and temporal operators in the decision IR (`971a202`).
- Escalation contract: the core gains an escalation band plus an
`EscalationReport`, and the engine wires the escalation report into
`decide` (`ed6c186`, `b6987ea`).
- Per-kind calibration analysis (`64c3094`); the benchmark runner threads
the decision family through the JevBench engine arm (`29ffbd4`).
- The `opencodifier-config` crate — configuration as a first-class,
versioned artifact (`1e859d0`).

### Vision rung (opt-in, no weights shipped)

- SigLIP2 zero-shot vision contract in the model crate, its ONNX
transport under D11's dependency rules, a serving embedder behind the
`InferenceBackend` trait, and an image-capable chat transport
(`ffc6e2c`, `ba60a48`, `9779a36`, `0cb1bda`).
- CLI `--image` input and the vision rung (`a1f5476`); image reads are
bounded (`f27e997`).

### Interfaces

- MCP `codify_gate` — a compact agent-facing gate tool (`a86ecdc`).
- `/v1/feedback` plus a CLI append path for the feedback loop
(`df7be28`).
- Lane presets — table, expansion, and `lane list/show/use` (`e73a943`).
- CI recipe controls for test selection, queue, stale-process detection,
and risk gating (`2079112`).

### Site

- Playground QA tool with real confidence and honest abstain output
(`bb8a76d`).
- Game library additions — the pac-maze shell plus the Pyraminx and
Skewb Tier-1 games (`d2b9178`, `c70c5aa`).

### Benchmarks and research tooling

- A llama.cpp decision-inference fork skeleton (D41) (`e851329`).
- A 4-worker concurrent teacher pass and a trainer v6 partial manifest
with session-3 verification (`a37e5cf`, `03796dd`).
- Per-family calibration analysis runner (`8715f17`).
- Arms: the d1-2B survey (JevBench-231 0.7489 via `/v1/systemone`), the
Jev-27 contamination audit with a Modal burst, and the 27B JevBench
probe (0.8658 accuracy, ECE 0.038 on 2xV100) (`98ec94f`, `941f0d6`,
`a267315`).

### Fixed

- The teacher pass survives transient server failures (`1fd4b92`).
- Internal config-crate path dependencies are pinned (`c6b3b1a`).
- `betacf` bindings are renamed for clippy's `similar_names` lint
(`e77105f`).
- Release scripts use the `hf` CLI (Hub 1.x removed `huggingface-cli`)
with a fallback, and both org placeholders are substituted in the E1-E
model card (`a550989`, `5fc7783`).

### Release validation

- Gate of record: GitForge pipeline receipt for the release commit.
Expand Down
4 changes: 4 additions & 0 deletions crates/opencodifier-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,10 @@ pub enum LaneSubcommand {
/// The lane name.
#[arg(value_name = "NAME")]
name: String,
/// Overwrite the target file if it already exists. Without this
/// flag an existing file is refused, never clobbered.
#[arg(long)]
force: bool,
/// Where to write the config. Default: `./opencodifier.json`.
#[arg(long, value_name = "PATH")]
out: Option<PathBuf>,
Expand Down
69 changes: 50 additions & 19 deletions crates/opencodifier-cli/src/decide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! printed; only the exit code distinguishes it (2, or 0 with
//! `--abstain-is-success`).

use std::io::Read;
use std::path::{Path, PathBuf};

use opencodifier_core::{
Expand Down Expand Up @@ -85,6 +86,24 @@ fn with_policy_override(
.map_err(CliError::from)
}

/// Reads at most [`MAX_ENCODED_IMAGE_BYTES`] + 1 bytes from `path`.
///
/// The read is capped rather than size-checked first: a device node or a
/// FIFO reports a length of zero but yields unbounded data, so a
/// metadata-based ceiling check would let an attacker-controlled path
/// stream gigabytes into memory before the rejection fires.
fn read_bounded(path: &Path) -> Result<Vec<u8>, CliError> {
let file = std::fs::File::open(path).map_err(|error| {
CliError::input(CODE_UNREADABLE_INPUT, format!("{}: {error}", path.display()))
})?;
let mut bytes = Vec::new();
let limit = u64::try_from(MAX_ENCODED_IMAGE_BYTES).unwrap_or(u64::MAX);
file.take(limit.saturating_add(1)).read_to_end(&mut bytes).map_err(|error| {
CliError::input(CODE_UNREADABLE_INPUT, format!("{}: {error}", path.display()))
})?;
Ok(bytes)
}

/// Attaches `--image` files to the request's state.
///
/// Each file's media type is detected from the magic bytes (PNG or
Expand All @@ -102,9 +121,7 @@ fn with_policy_override(
fn with_images(request: &DecisionRequest, paths: &[PathBuf]) -> Result<DecisionRequest, CliError> {
let mut state = request.state().clone();
for path in paths {
let bytes = std::fs::read(path).map_err(|error| {
CliError::input(CODE_UNREADABLE_INPUT, format!("{}: {error}", path.display()))
})?;
let bytes = read_bounded(path)?;
if bytes.len() > MAX_ENCODED_IMAGE_BYTES {
return Err(CliError::input(
CODE_UNSUPPORTED_IMAGE,
Expand Down Expand Up @@ -203,48 +220,46 @@ mod tests {
.unwrap()
}

fn temp_image(bytes: &[u8], tag: &str) -> PathBuf {
let dir = std::env::temp_dir();
let path = dir.join(format!("oc-decide-image-{tag}.bin"));
/// Writes `bytes` into a private tempdir; the guard deletes it on
/// drop, so no fixed names in the shared temp dir and no manual
/// cleanup to swallow failures.
fn temp_image(dir: &tempfile::TempDir, bytes: &[u8], name: &str) -> PathBuf {
let path = dir.path().join(name);
std::fs::write(&path, bytes).unwrap();
path
}

#[test]
fn with_images_accepts_png_magic() {
let png = temp_image(&PNG_MAGIC, "png");
let dir = tempfile::tempdir().unwrap();
let png = temp_image(&dir, &PNG_MAGIC, "png.bin");
let rebuilt = with_images(&base_request(), &[png]).unwrap();
assert_eq!(rebuilt.state().images().len(), 1);
let _ = std::fs::remove_file(std::env::temp_dir().join("oc-decide-image-png.bin"));
}

#[test]
fn with_images_accepts_jpeg_magic() {
let jpeg = temp_image(&JPEG_MAGIC, "jpeg");
let dir = tempfile::tempdir().unwrap();
let jpeg = temp_image(&dir, &JPEG_MAGIC, "jpeg.bin");
let rebuilt = with_images(&base_request(), &[jpeg]).unwrap();
assert_eq!(rebuilt.state().images().len(), 1);
let _ = std::fs::remove_file(std::env::temp_dir().join("oc-decide-image-jpeg.bin"));
}

#[test]
fn with_images_attaches_multiple_in_order() {
let png = temp_image(&PNG_MAGIC, "multi-png");
let jpeg = temp_image(&JPEG_MAGIC, "multi-jpeg");
let dir = tempfile::tempdir().unwrap();
let png = temp_image(&dir, &PNG_MAGIC, "a.png");
let jpeg = temp_image(&dir, &JPEG_MAGIC, "b.jpg");
let rebuilt = with_images(&base_request(), &[png, jpeg]).unwrap();
assert_eq!(rebuilt.state().images().len(), 2);
for tag in ["multi-png", "multi-jpeg"] {
let _ = std::fs::remove_file(
std::env::temp_dir().join(format!("oc-decide-image-{tag}.bin")),
);
}
}

#[test]
fn with_images_refuses_unknown_magic() {
let junk = temp_image(b"not-an-image", "junk");
let dir = tempfile::tempdir().unwrap();
let junk = temp_image(&dir, b"not-an-image", "junk.bin");
let error = with_images(&base_request(), &[junk]).unwrap_err();
assert_eq!(error.code(), CODE_UNSUPPORTED_IMAGE);
let _ = std::fs::remove_file(std::env::temp_dir().join("oc-decide-image-junk.bin"));
}

#[test]
Expand All @@ -271,6 +286,22 @@ mod tests {
assert!(error.to_string().contains("ceiling"));
}

#[test]
fn with_images_refuses_a_directory_path_as_unreadable() {
let dir = tempfile::tempdir().unwrap();
let error = with_images(&base_request(), &[dir.path().to_path_buf()]).unwrap_err();
assert_eq!(error.code(), CODE_UNREADABLE_INPUT);
}

#[test]
fn read_bounded_stops_at_the_ceiling_plus_one() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("huge.bin");
std::fs::write(&path, vec![0u8; MAX_ENCODED_IMAGE_BYTES + 4096]).unwrap();
let bytes = read_bounded(&path).unwrap();
assert_eq!(bytes.len(), MAX_ENCODED_IMAGE_BYTES + 1);
}

#[test]
fn finish_maps_decisive_and_abstain_paths() {
assert!(finish(DecisionOutcome::Accept, false).is_ok());
Expand Down
40 changes: 37 additions & 3 deletions crates/opencodifier-cli/src/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ pub(crate) fn run(command: &LaneSubcommand) -> Result<(), CliError> {
.map_err(|error| CliError::engine("config.serialize", error.to_string()))?;
output::print_line(&text)
}
LaneSubcommand::Use { name, out } => {
LaneSubcommand::Use { name, force, out } => {
let config = expand_or_err(name)?;
let path =
out.clone().unwrap_or_else(|| PathBuf::from(opencodifier_config::PROJECT_FILE));
if path.exists() && !force {
return Err(CliError::input(
CODE_UNWRITABLE_OUTPUT,
format!("{} already exists; pass --force to overwrite it", path.display()),
));
}
let text = serde_json::to_string_pretty(&config)
.map_err(|error| CliError::engine("config.serialize", error.to_string()))?;
std::fs::write(&path, text).map_err(|error| {
Expand Down Expand Up @@ -98,18 +104,46 @@ mod tests {
fn use_writes_the_config_file() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("project.json");
run(&LaneSubcommand::Use { name: "zero".to_owned(), out: Some(out.clone()) }).unwrap();
run(&LaneSubcommand::Use { name: "zero".to_owned(), force: false, out: Some(out.clone()) })
.unwrap();
let text = std::fs::read_to_string(&out).unwrap();
let config: serde_json::Value = serde_json::from_str(&text).unwrap();
assert!(config.get("lane").is_some(), "{text}");
}

#[test]
fn use_refuses_to_clobber_an_existing_file_without_force() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("project.json");
std::fs::write(&out, "{\"existing\": true}").unwrap();
let error = run(&LaneSubcommand::Use {
name: "zero".to_owned(),
force: false,
out: Some(out.clone()),
})
.unwrap_err();
assert_eq!(error.code(), CODE_UNWRITABLE_OUTPUT);
assert_eq!(std::fs::read_to_string(&out).unwrap(), "{\"existing\": true}");
}

#[test]
fn use_overwrites_an_existing_file_with_force() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("project.json");
std::fs::write(&out, "{\"existing\": true}").unwrap();
run(&LaneSubcommand::Use { name: "zero".to_owned(), force: true, out: Some(out.clone()) })
.unwrap();
let text = std::fs::read_to_string(&out).unwrap();
assert!(!text.contains("existing"), "{text}");
}

#[test]
fn use_refuses_an_unknown_lane_before_writing() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("never.json");
let error =
run(&LaneSubcommand::Use { name: "turbo".to_owned(), out: Some(out) }).unwrap_err();
run(&LaneSubcommand::Use { name: "turbo".to_owned(), force: false, out: Some(out) })
.unwrap_err();
assert_eq!(error.code(), CODE_UNKNOWN_LANE);
assert!(!dir.path().join("never.json").exists());
}
Expand Down
2 changes: 1 addition & 1 deletion docs/ESCALATION_LANES_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ CLI surface:
```
opencodifier lane list # table with cost/accuracy/latency envelope
opencodifier lane show <name> # the exact resolved config it expands to
opencodifier lane use <name> # writes the chosen lane into the config
opencodifier lane use <name> # writes the chosen lane into the config (refuses to overwrite unless --force)
```

---
Expand Down
51 changes: 51 additions & 0 deletions docs/handoffs/SESSION_HANDOFF_2026-11-01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Session handoff — 2026-11-01 (pipeline e2e capture-drift fix)

Session of record: 8fa0a40c (prior, died on API/context errors) resumed into
4712ff05. This file records state and next steps; `docs/PLAN.md` stays the
live plan.

## What happened

1. Push of `69c6869` (style(cli) rustfmt fix) to GitForge succeeded; run
`64f14f2b` launched at 07:02 UTC.
2. Run result (from the store, `/var/lib/gitforge/gitforge.db`): 6/7 lanes
green — lint, test, doc, supply-chain, coverage, e2e-build OK. **e2e
failed**: 30/30 HTTP checks passed, but the recipe-capture check found 8
STALE captures: `ci-risk-gate`, `ci-stale-process`, `document-relevance`,
`escalation`, `skill-selection`, `strict-verify`, `task-classification`,
`verification`.
3. Root cause: 5 engine commits landed after the captures were last recorded
(`33ff9c5` rotation readout, `4d9e755` CP-certified early exit, plus
test/fix/fold commits). The rotation readout changes response
distributions; `recipes/expected/*-response.json` must re-record.
The local Oct-23 `target/release/opencodifier` reproduced the old
captures — stale binary, not evidence of correctness. CI's fresh build
is right.
4. Fix in flight: rebuild release binary to
`CARGO_TARGET_DIR=/nas/Dev/Targets/OpenCodifier`, run
`python3 recipes/capture.py --binary <new>`, commit the re-recorded
`recipes/expected/` files, push, confirm pipeline green.
5. Prior-session audit agents (schema adapters, scripts/CI, engine, core
audit) all died on the upstream 7k-ITPM Groq limit or context limits —
the code-review-style audits did NOT complete. Re-run them with smaller
scopes if still wanted.

## Unrelated observation (not OC)

GitForge repo `gitforge` itself has 3 failed runs at 2026-11-01 09:10 UTC:
`detect-changes` → infrastructure_failure, "pull access denied for
dsc-ci-rust" — the runner image is missing on the Docker host. Needs an
image rebuild/push; OC work is unaffected.

## Task state at handoff

- e2e capture re-record + push + green pipeline: **DONE 2026-11-01.**
`f724f94` (re-record) + `6764c00` (aegis baseline regen; recipe
fingerprint drift fixed, ledger trimmed) → run `2d3fa273` fully green,
all 6 lanes succeeded.
- Release cut + deploy + GitForge/Amortyx clean-binary integration
(Phase 27, from `docs/planning/prebuilt-binary-release-and-onboarding-2026-10-29.md`):
**open** — the clean patch release follows the green pipeline; attestation
must show 0 dirty paths.
- Phase 26 #20 (Amortyx shadow promotion window): still collecting, ≥1 week
from 2026-10-26; decision due ~2026-11-02+ from shadow counters.
Loading
Loading