0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

0.1b — validate lab.toml on extension load with field-precise errors (closes #16) - #29

Merged
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation
Jun 29, 2026
Merged

0.1b — validate lab.toml on extension load with field-precise errors (closes #16)#29
jack-champagne merged 2 commits into
mainfrom
rchari/0.1b-lab-validation

Conversation

@Rchari1

Copy link
Copy Markdown
Member

Stacked on #28 (0.1a). Do not merge.

A partner lab's lab.toml is the only place hardware params enter a solve; β had no validation, so a malformed/mistyped config silently solved against the wrong hardware or failed opaquely mid-solve. This validates it on extension load against the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a field-precise error (offending key + dotted path), non-fatally.

What's here

  • src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion) + checkLabTomlabsent | valid | invalid{errors}, via the single@amicode/schema validator (no second validation path).
  • extension.ts activate(): validates on load; invalid → showErrorMessage with the first field-precise error + the full list in the "Amicode — runs" channel. Non-fatal — the rest of the extension still activates.
  • amicode.labToml config (path; empty → ~/.amico/lab.toml).
  • lab.toml.example: stamped schema_version = "1" so the shipped starter validates.

Tests (lab_config.test.ts, 13)

Field-precise negative matrix (missing / wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity with @amicode/schema.validateFile (asserts the full errors array — proves single-path), and shipped-lab.toml.example conformance. Extension 63 green; typecheck clean.

Review

Adversarial code review: no must-fix / should-fix. Verified empirically that validateFile is throw-safe on missing/garbage/directory inputs (so the load seam can't break activation), and that lab.schema.json exactly matches the shipped lab.toml.example (a real install won't red on load).

🤖 Generated with Claude Code

@jack-champagnejack-champagne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code's correct — reproduced all 7 ACs against real ajv output; the load-time check delegates to the single @amicode/schema validator and is non-fatal. Approving the code.

Two nits that live in #28's schema (flagging here since this is the PR that owns the "no silent wrong-hardware" story): delta_GHz has no range bound while every other [transmon] field does — a sign-flipped or garbage anharmonicity passes validation, the exact class this slice exists to catch; and the only valid fixture is demo-lab, no Schuster profile (the PRD's demo forcing-function).

Tests + Phase 1: the negative matrix is partial vs #16 — it hits levels / drive_max / schema_version / unknown-key but not the range bounds (omega_GHz ≤ 100, drive_max_GHz ≤ 10) or lab.name minLength, and the "parity over the shared negative-fixture corpus" AC is exercised on a single input (inline). Two Phase-1 prerequisites this surfaces: (1) there is no test harness for the VS Code host layer — the on-load toast/channel path is untested, and Phase 1 stacks more UI (inspector multi-run, catalog tree) on that same seam, so an extension-host integration approach should land before it compounds; (2) lab.toml has no provides block yet — the cloud solve-service (aws-infra) keys on lab provides to reproduce the runtime and validate requires ⊆ provides, so that dimension + its validation is a cloud/Phase-1 prerequisite, not optional polish.

Sequencing, not a change: stacked on #28 (CHANGES_REQUESTED), and the tests hardcode #28's error strings — rebase + re-run after #28 settles, and don't merge ahead of it.

if (lab.state === "invalid") {
runsChannel.appendLine(`[lab] ${lab.path} is INVALID:`);
for (const e of lab.errors) runsChannel.appendLine(` ${e}`);
void vscode.window.showErrorMessage(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] this showErrorMessage + output-channel path is the one surface the unit tests can't reach — no VS Code host in CI, so checkLabToml is covered but the toast/channel wiring isn't. I'll verify it by hand on the packaged VSIX (bad ~/.amico/lab.toml → reload → toast + "Amicode — runs" list). Non-blocking — but it's the seam Phase 1's UI work compounds on, so it wants a host-level test approach soon.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the toast/channel wiring stays unit-untestable (no VS Code host in CI). Noted as a Phase-1 prerequisite (a host-level/integration-test approach before the inspector/catalog UI compounds on this seam); not adding host-test infra in this slice. checkLabToml itself is covered.

it("unrecognized schema_version → version-specific error", () =>
expect(has(errs(VALID.replace('schema_version = "1"', 'schema_version = "9"')), "/schema_version: unrecognized version")).toBe(true));

it("parity: checkLabToml uses the SAME validator as @amicode/schema directly (no second path)", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[test] this proves parity on one input; #16's AC says "over the shared negative-fixture corpus." Delegation makes drift structurally impossible so it's not urgent — but loop the corpus to meet the AC literally, and add the range-bound cases the matrix above skips (omega_GHz ≤ 100, drive_max_GHz ≤ 10, lab.name minLength).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the parity test now loops a corpus (valid + missing/wrong-type/out-of-range/unknown-key/absent+unrecognized-version), asserting checkLabToml === validateFile on each, and the range-bound cases (omega_GHz≤100, drive_max_GHz≤10, the now-bounded delta_GHz, lab.name minLength) are added. Plus a Schuster-profile valid fixture (negative-δ, 4 levels).

Rchari1and others added 2 commits June 28, 2026 16:28
…errors (closes#16)
A partner lab's lab.toml is the only place hardware params enter a solve; β had no
validation, so a malformed/mistyped config silently solved against the wrong
hardware or failed opaquely mid-solve. This validates it on extension load against
the shared @amicode/schema lab schema (defined in 0.1a) and surfaces a
field-precise error (offending key + dotted path), non-fatally.
- src/lab_config.ts: resolveLabTomlPath (default ~/.amico/lab.toml, ~ expansion)
+ checkLabToml → absent | valid | invalid{errors}, via the SINGLE @amicode/schema
validator (no second validation path).
- extension.ts activate(): validates on load; invalid → showErrorMessage with the
first field-precise error + full list in the "Amicode — runs" output channel.
- amicode.labToml config setting (path; empty → ~/.amico/lab.toml).
- lab.toml.example: stamped schema_version = "1" so the shipped starter validates.
- Tests (lab_config.test.ts, 13): field-precise negative matrix (missing /
wrong-type / out-of-range / unknown-key / absent+unrecognized version), parity
with @amicode/schema.validateFile (single-path proof), shipped-example conforms.
Extension 63 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/Schuster (rebased on #28 rename)
Address Jack's #29 nits:
- Loop the parity assertion over a CORPUS (valid + missing/wrong-type/out-of-range/
unknown-key/absent+unrecognized-version) asserting checkLabToml === @amicode/schema
validateFile on each — was a single input (#16 "over the corpus").
- Add the range-bound negatives the matrix skipped: omega_GHz≤100, drive_max_GHz≤10,
delta_GHz (now bounded in #28), and lab.name minLength — each field-precise.
- Add a Schuster-profile valid fixture (negative-δ convention, 4 levels) alongside
demo-lab — the PRD demo forcing-function + second real-shaped profile.
Rebased onto #28's manifest.toml→run.toml rename (lab files don't reference the
run-dir header, so clean). extension 64 (+1 packaging skip) green.
Non-blocking (Jack): the toast/channel seam has no VS Code host test (no host in CI)
and lab.toml has no `provides` — both are Phase-1 prerequisites (provides ties to the
0.1a-follow env-contract), noted for that work, not this slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Rchari1
Rchari1force-pushed the rchari/0.1b-lab-validation branch from 5def952 to ade5a6dCompareJune 28, 2026 20:29
@jack-champagne
jack-champagne changed the base branch from rchari/0.1a-schema-package to mainJune 29, 2026 03:47
@jack-champagne
jack-champagne merged commit a14836b into mainJun 29, 2026
3 checks passed
@kateebonnerkateebonner self-assigned this Jul 1, 2026
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.

3 participants

@Rchari1@jack-champagne@kateebonner