Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .agents/skills/finalize-phase/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ Inspect the current worktree, `HEAD`, tracked and untracked changes, and commits
base against `main`: prefer the merge base with `origin/main` when available, otherwise use local
`main`. Record the exact base.

Fetch `origin` before treating generated contracts or release evidence as final:

```bash
git fetch origin
git rev-parse HEAD
git rev-parse origin/main
git merge-base HEAD origin/main
```

The task branch contains the current `origin/main` only when the merge-base equals `origin/main`.
If it does not, STOP. Do not merge or rebase as part of this check. Require an explicit
synchronization decision before regenerating OpenAPI, generated TypeScript, or release evidence.

Review the union of:

- committed changes from the base through `HEAD`;
Expand Down
14 changes: 14 additions & 0 deletions .agents/skills/finish-worktree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ If a substantial unrelated change is present, stop and report it instead of sile

## 3. Perform targeted validation

Confirm the task branch contains the current `origin/main` before running final validation of
generated contracts or release evidence:

```bash
git fetch origin
git rev-parse HEAD
git rev-parse origin/main
git merge-base HEAD origin/main
```

The branch contains `origin/main` only when the merge-base equals `origin/main`. If it does not,
STOP. Do not merge or rebase as part of this check. Require an explicit synchronization decision
before regenerating OpenAPI, generated TypeScript, or release evidence.

Determine validation from the files and behavior changed by this task.

Run the relevant targeted:
Expand Down
13 changes: 13 additions & 0 deletions .claude/skills/finalize-phase/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ Inspect the current worktree, `HEAD`, tracked and untracked changes, and commits
base against `main`: prefer the merge base with `origin/main` when available, otherwise use local
`main`. Record the exact base.

Fetch `origin` before treating generated contracts or release evidence as final:

```bash
git fetch origin
git rev-parse HEAD
git rev-parse origin/main
git merge-base HEAD origin/main
```

The task branch contains the current `origin/main` only when the merge-base equals `origin/main`.
If it does not, STOP. Do not merge or rebase as part of this check. Require an explicit
synchronization decision before regenerating OpenAPI, generated TypeScript, or release evidence.

Review the union of:

- committed changes from the base through `HEAD`;
Expand Down
14 changes: 14 additions & 0 deletions .claude/skills/finish-worktree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ If a substantial unrelated change is present, stop and report it instead of sile

## 3. Perform targeted validation

Confirm the task branch contains the current `origin/main` before running final validation of
generated contracts or release evidence:

```bash
git fetch origin
git rev-parse HEAD
git rev-parse origin/main
git merge-base HEAD origin/main
```

The branch contains `origin/main` only when the merge-base equals `origin/main`. If it does not,
STOP. Do not merge or rebase as part of this check. Require an explicit synchronization decision
before regenerating OpenAPI, generated TypeScript, or release evidence.

Determine validation from the files and behavior changed by this task.

Run the relevant targeted:
Expand Down
28 changes: 22 additions & 6 deletions .claude/tools/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import argparse
import json
import os
import shutil
import subprocess
import sys
Expand Down Expand Up @@ -97,7 +98,7 @@ def _has_selected_test(root: Path, marker: str, keyword: str) -> bool:
return False


def _command_responds(argv: list[str]) -> bool:
def _command_responds(argv: list[str], *, env: dict[str, str] | None = None) -> bool:
"""Whether a subcommand actually exists, probed via its own `--help`.

A file existing is not the same as a command being implemented: `src/labbridge/cli.py` appears
Expand All @@ -109,14 +110,27 @@ def _command_responds(argv: list[str]) -> bool:
try:
return (
subprocess.run(
[*argv, "--help"], capture_output=True, timeout=15, check=False
[*argv, "--help"], capture_output=True, timeout=15, check=False, env=env
).returncode
== 0
)
except Exception:
return False


def _checkout_env(root: Path) -> dict[str, str]:
"""Environment that resolves `labbridge` to this checkout rather than to an editable install.

An editable install can point at a different worktree, so a probe without this would report on
someone else's bytes.
"""
environment = dict(os.environ)
existing = environment.get("PYTHONPATH")
source = str(root / "src")
environment["PYTHONPATH"] = f"{source}{os.pathsep}{existing}" if existing else source
return environment


def _tool_gate(key: str, command: str, tool: str, present: bool, missing_reason: str) -> Gate:
if not present:
return Gate(key, command, SCAFFOLDED, missing_reason)
Expand All @@ -134,7 +148,9 @@ def collect(root: Path) -> list[Gate]:
compose = any(
(root / f).exists() for f in ("docker-compose.yml", "docker-compose.yaml", "compose.yaml")
)
artifacts_cmd = _command_responds(["labbridge", "validate-artifacts"])
artifacts_cmd = _command_responds(
[sys.executable, "-m", "labbridge.cli", "validate-artifacts"], env=_checkout_env(root)
)
has_integration = _has_marked_test(root, "integration")
has_replay = _has_selected_test(root, "integration", "test_replay_determinism")
has_migration_test = _has_selected_test(root, "integration", "migration")
Expand Down Expand Up @@ -224,11 +240,11 @@ def collect(root: Path) -> list[Gate]:
),
Gate(
"artifacts",
"labbridge validate-artifacts",
"PYTHONPATH=src python -m labbridge.cli validate-artifacts",
LIVE if artifacts_cmd else SCAFFOLDED,
"command responds to --help"
"command responds to --help; verifies the committed artifacts/ tree"
if artifacts_cmd
else "`labbridge validate-artifacts` is not implemented yet (ROADMAP Slice 3)",
else "`validate-artifacts` is not implemented yet (ROADMAP Slice 3)",
),
Gate(
"compose",
Expand Down
22 changes: 9 additions & 13 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,20 @@ scripts/hooks/* text eol=lf
# SHA256SUMS.txt checksums the published documents byte-for-byte against their LF content. Pin LF so
# a Windows checkout cannot rewrite them to CRLF and make `sha256sum -c SHA256SUMS.txt` fail on a
# file nobody edited.
*.md text eol=lf
# Two trailing spaces are a hard line break in Markdown, so `git diff --check` must not read them
# as whitespace errors in the published documents.
*.md text eol=lf whitespace=-trailing-space
*.toml text eol=lf
SHA256SUMS.txt text eol=lf

# Released integrity artifacts and their source fixtures are exact bytes. Do not let checkout
# line-ending conversion invalidate their recorded hashes.
artifacts/source-capture/** -text
artifacts/experiment-passport/** -text
artifacts/fault-campaign/** -text
# Released integrity artifacts and the fixture inputs they are reproduced from are exact bytes. Do
# not let checkout line-ending conversion invalidate their recorded hashes. These cover whole trees
# rather than one rule per directory: a manifest-covered member added under a new artifact or
# fixture directory is protected when it lands, not when someone remembers to extend this list.
artifacts/** -text
artifacts/fault-campaign/raw-results.csv -text whitespace=-trailing-space
artifacts/cv-passport-demo/** -text -eol
artifacts/gamry-dta-cv/** -text
artifacts/echemdb-cv-exchange/** -text
artifacts/galvanostatic-electrolysis/** -text
fixtures/source/synthetic-replay-cv-opaque.csv -text
fixtures/source/synthetic-gamry-cv.dta -text
fixtures/source/synthetic-galvanostatic-electrolysis.csv -text
fixtures/source/synthetic-galvanostatic-electrolysis.provenance.json -text
fixtures/** -text
frontend/public/demo-fixtures/synthetic-cv-passport-demo.csv -text
frontend/src/generated/api-v1.ts text eol=lf
src/labbridge/evidence/schemas/** -text whitespace=-trailing-space
Expand Down
102 changes: 102 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: CI

# Pull-request gating for the checks that need no services. The integration, migration, and
# fault-campaign suites need PostgreSQL and MinIO and are deliberately not run here; they stay
# explicit higher-layer checks so a green PR never implies a durability guarantee it did not test.
on:
pull_request:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash

jobs:
python-gates:
# Both platforms on purpose. Committed evidence is verified by SHA-256, and a checkout that
# rewrites line endings changes those bytes on one platform only, so a Linux-only run cannot
# see it.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
# The whitespace check below diffs against the pull-request base commit.
fetch-depth: 0

- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip

- name: Install
run: pip install -e ".[dev]"

- name: Documentation consistency
run: python scripts/check_docs.py --strict

- name: Normative document manifest
run: sha256sum -c SHA256SUMS.txt

- name: Agent system
run: python .claude/tools/check_agent_system.py

- name: ruff format
run: ruff format --check src/ tests/ scripts/ migrations/ .claude/hooks/ .claude/tools/

- name: ruff check
run: ruff check src/ tests/ scripts/ migrations/ .claude/hooks/ .claude/tools/

- name: mypy --strict
run: mypy --strict src/

- name: Offline tests
run: pytest -q -m "not slow and not data and not integration"

- name: Committed artifact verification
run: python -m labbridge.cli validate-artifacts

# Scoped to what this change introduces: the published documents predate the gate and use
# trailing double spaces as Markdown hard breaks.
- name: Whitespace and conflict markers in this change
if: github.event_name == 'pull_request'
run: git diff --check "${{ github.event.pull_request.base.sha }}" HEAD

frontend-contract:
# The OpenAPI/generated-type drift gate. Its Python half runs in the offline suite above; this
# is the half that proves the committed TypeScript still matches the committed contract.
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install
run: npm ci

- name: Generated API types match the contract
run: npm run api:check

- name: Typecheck
run: npm run typecheck

- name: Unit tests
run: npm test -- --run
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ labbridge reconcile

Reclaims expired leases, closes abandoned attempts with durable outcomes, and classifies stored
objects without deleting unexplained bytes. A worker runs the same reconciliation pass at startup.
It does not cover source intake: `SourceArtifactService.reconcile` implements the same recovery for
a source left `pending` after an intake interrupted between the object write and the metadata
commit (`docs/FAILURE_MATRIX.md` F-048), but has no operator command, because its `commit`/
`quarantine` writes are unguarded against a concurrent `intake()` of the same content identity and
exposing it needs that race closed first.

## Evidence status

Expand Down Expand Up @@ -287,10 +292,9 @@ single-user CV Passport candidate is under
domain classification and unfamiliar-viewer acceptance records described above. The
Phase 4 candidate under [`artifacts/gamry-dta-cv`](artifacts/gamry-dta-cv) follows the same boundary:
its code, tests, and candidate Package justify `implemented`, not `demonstrated`, in this worktree.
Phase 7 synthetic-replay reliability evidence is released under
[`artifacts/fault-campaign`](artifacts/fault-campaign). It demonstrates the recorded fault campaign,
backup/restore, replay comparison, and full stored-object verification; it does not demonstrate
observed replay or live execution.
Phase 7 synthetic-replay reliability evidence is committed under
[`artifacts/fault-campaign`](artifacts/fault-campaign). The recorded producer is a clean committed
tree that contains `origin/main`. It does not demonstrate observed replay or live execution.
The galvanostatic electrolysis candidate under
[`artifacts/galvanostatic-electrolysis`](artifacts/galvanostatic-electrolysis) packages explicit time,
current, and potential series while reporting chemical analysis as unavailable. It makes no
Expand All @@ -304,10 +308,11 @@ conversion, selectivity, yield, product-assignment, or Faradaic-efficiency claim
- `labbridge reconcile` and worker-startup reconciliation are on-demand rather than a continuous
daemon. Retry scheduling is durable and bounded, but no continuously running scheduler is
provided.
- `labbridge validate-artifacts` defaults to `--mode bundle-only`, verifies bundle members locally,
and reports `partial`; it does not contact object storage. `--mode full` additionally checks every
referenced object for existence, byte size, and SHA-256 and reports `complete` only when those checks
pass.
- `labbridge validate-artifacts` with no `--bundle` verifies every closed manifest under `artifacts/`
and under the local `data/bundles/` root, and exits non-zero when it finds nothing to verify. It
defaults to `--mode bundle-only`, verifies bundle members locally, and reports `partial`; it does
not contact object storage. `--mode full` additionally checks every referenced object for existence,
byte size, and SHA-256 and reports `complete` only when those checks pass.
- Crash recovery is tested across real process boundaries after lease acquisition, after adapter
return, during object upload, after upload before the outcome transaction, after commit before
acknowledgement, and during evidence export. Campaign cancellation with a leased job is also
Expand Down
4 changes: 2 additions & 2 deletions SHA256SUMS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ a70a90ce5f4920d42f1d38cab5785f3ca19206b4fb98f2464718d813b08a752e *docs/DATA_STRA
cc9585ebb400cecfbc796296753f48f47fe06fb18e0393037d3315b62a9c6819 *docs/DEVELOPMENT_WORKFLOW.md
5f4a3475482f6485bca11c02f5b553a99c440c1eb164867dba118cfa73b35c83 *docs/FAILURE_MATRIX.md
11ba8a68a5e615b5f03ed68c08e8724ed5abd3c1f2f84cb50f0a6063ff042517 *docs/OPERATOR_RUNBOOK.md
44b3ff8b19669513536732c7bb3434236ac78f51b6f52ea593aeed9521a3e1aa *docs/ROADMAP.md
f0916117156813286c7ba9c215930682fbf4ecb620efef13da059ffaae9a8253 *docs/ROADMAP.md
b0fe3118afa8403a2ec7ee138479fd24f93135a34957b1c75a5624d6839c786c *docs/SIMULATOR_MODEL.md
b36f89a42fdaa1dda18bc891c5d6f30a2131e4ffc22538407b3713e7d9e0961d *docs/SPEC.md
fde295bd73f5959c68d5fe2690fcac6286384954b82a19733acc4624465701bb *docs/SPEC.md
e74afe9f11555bf79778f53a1388d21bba3434b20e558f7ee6d028fbacd740de *docs/superpowers/specs/2026-08-12-cv-passport-demo-design.md
9 changes: 9 additions & 0 deletions artifacts/echemdb-cv-exchange/LIMITATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,14 @@ contact area, scan rate, and cycle information are omitted and listed in `mappin
EchemDB figure type is an explicitly asserted lossy projection; LabBridge `data_origin` and
`execution_mode` remain independently represented in `labbridge-provenance.json`.

Two boundaries of that companion arrangement are explicit rather than resolved. `datapackage.json`
declares one resource, `cv.csv`, and does not reference `labbridge-provenance.json`, so a consumer
reading the package only as Frictionless defines it receives the table without LabBridge origin,
execution mode, or evidence identities; those are found by reading the companion beside it.
`cv.csv` itself carries no synthetic marker in its filename or its columns, because its field set
is fixed by the pinned EchemDB `figureDescription`. Within this package the synthetic origin is
declared by `source.citationKey`, `source.originalFilename`, `figureDescription.type`, and the
companion; detached from them the table does not identify itself as synthetic.

The capability status is `implemented`; this uncommitted candidate is not evidence of clean-checkout
demonstration, EchemDB ingestion, EchemDB publication, or production deployment.
6 changes: 3 additions & 3 deletions artifacts/echemdb-cv-exchange/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
"sha256": "a7cda1623b5170e94d53ec8beac6969568fee02fcfa0539cd828352dae38b577"
},
{
"byte_size": 1866,
"byte_size": 2622,
"name": "LIMITATIONS.md",
"sha256": "2cd60c07b092bb4216fb704fcda233e37824d4a48ea222b0f119a327d52b60e1"
"sha256": "94dc644c23cab3fabae5a082cc4cdb43bfcd8e4fe31ffa84211a64e1fa091f9e"
},
{
"byte_size": 12651,
Expand Down Expand Up @@ -87,7 +87,7 @@
"sha256": "5ad1ecbdad8e23c9260d29e4fe7bde569cbb79a1899c8c1430310647e466d236"
}
],
"files_digest": "565c900337c6db637d74a6647e1685084df93dc0f33e778060f55246864f6909",
"files_digest": "b582c2c8efd880b26fa11dd1eed415053dc743cb875aee809d72035d4c9a1d1e",
"observation_id": "cv-observation:f88ccc6e9ad289c674d8f3ba4b9bcc20",
"producing_versions": {
"adapter": "echemdb-cv/1",
Expand Down
2 changes: 1 addition & 1 deletion artifacts/fault-campaign/LIMITATIONS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Limitations

- The released run used generated synthetic fixture bytes in replay mode; it did not use observed HER archive bytes.
- The released run used synthetic bytes in replay mode.
- Future observed live execution remains outside Phase 7 and was not run.
- Results cover only the recorded environment, seeds, and injected boundaries.
2 changes: 1 addition & 1 deletion artifacts/fault-campaign/REPRODUCE.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python scripts/reproduce_campaign_reliability.py --campaigns 100 --master-seed 20260813 --database-name labbridge_phase7_fault_release2 --bucket labbridge-phase7-fault-release2 --output build/phase7-fault-campaign
python scripts/reproduce_campaign_reliability.py --campaigns 100 --master-seed 20260813 --database-name labbridge_phase7_fault_release3 --bucket labbridge-phase7-fault-release3 --output build/phase7-fault-campaign
Loading
Loading