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
121 changes: 121 additions & 0 deletions docs/evidence/v0.44.0-agent-loops/a0-001/repair-2-result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Implementer result — a0-001 repair-2 (CI lint and architecture contract)

STATUS: PASS

PACKET:
docs/evidence/v0.44.0-agent-loops/a0-001/repair-2.md (loopId a0-001-repair-2, parentLoop
a0-001, issue #1160, failed run 33071761401). Plus a reviewer mid-flight correction: the
initially planned fail-closed non-input property-target restriction was unauthorized; that
test step was removed and the property sink remains generic for any Element.

BASE_SHA:
failedCandidateSha bcc99b25df5a2ded515995b8fb0e3470bf87258e; worktree HEAD at repair start:
4e1c322df76849846c8254946515e2575c758255 (mergedSha, clean except the reviewer-owned untracked
repair-2.md packet)

CHANGED_FILES:

- packages/adapter-vite/**fixtures**/compiled-element-spike/counter.tsx (fixture button
gains explicit non-submitting `type='button'` — jsx-button-has-type)
- packages/adapter-vite/**fixtures**/compiled-element-spike/expected-program.json (required
deterministic-evidence update per packet instruction 1: the button now carries
`attrs: [["type","button"]]`; program bytes 2542 → 2626)
- packages/element/src/internal/compiled/program.ts (validateSpikeProgram returns a
freshly constructed, honestly narrowed PartProgramSpike instead of
`raw as unknown as PartProgramSpike`)
- packages/element/src/internal/compiled/runtime.ts (both prop-part `as unknown as
Record<string, unknown>` escapes replaced by a small local typed helper `propertySink()`
using a one-step structural assertion `el as SpikePropertySink`, where
`interface SpikePropertySink extends Element { [property: string]: unknown }`; dynamic
property assignment semantics retained for any Element — no input/allowlist restriction)
- packages/element/**tests**/compiled-part-program-spike.test.ts (pinned SSR string and
0.43-equivalent proxy builder updated for `type="button"`; new step 'program validation
still fails closed after the cast removal (repair-2)')
- docs/evidence/v0.44.0-agent-loops/a0-001/repair-2-result.md (this file)

TESTS_ADDED:

- element spike test, new step 'program validation still fails closed after the cast
removal (repair-2)': validateSpikeProgram still rejects a part-index/position mismatch
and a wrong version after the return-cast removal.
- The initially added step 'DOM property writes stay fail-closed for non-input targets'
was REMOVED per reviewer correction (it imposed an unauthorized semantic restriction). The
generic property-sink behavior is already pinned by the pre-existing steps: fresh
property initialization (SSR/creation structural equality), claim live-value
preservation with zero property writes, and subsequent signal writes in the granularity
and lazy-delivery steps.

RED_EVIDENCE:

- `deno lint packages/adapter-vite/__fixtures__/compiled-element-spike/counter.tsx` →
exit=1: `error[jsx-button-has-type]: button elements must have a type attribute` at
counter.tsx:37 (matches CI run 33071761401 fact 1).
- `deno task arch:check` → exit=1: three `[type-escape] production as unknown as is not in
the reviewed allowlist` hits at program.ts:258, runtime.ts:322, runtime.ts:327 (matches
CI fact 2).
- Test-first intermediate RED: with the retargeted-property step present (later removed
per reviewer correction), the element suite failed with `AssertionError: Expected function to
throw` against the pre-repair runtime. The kept validation step passed pre- and
post-repair; the production RED for this CI repair is the two gate failures above.

IMPLEMENTATION:

1. Fixture button now has `type='button'`; the frozen expected program and pinned SSR
string were regenerated/updated for the added static attribute (the ONLY resulting
evidence delta; generatedBytes 3404 → 3488, programBytes 2541 → 2625 embedded / 2626
file, instructionCount unchanged at 5; runtime allocation/activation counts unchanged).
2. program.ts: `validateSpikeProgram` now returns
`{ version: PART_PROGRAM_SPIKE_VERSION, tag: raw.tag, template: raw.template as
SpikeTreeNode[], parts }` — `raw.tag` is narrowed by the never-returning `fail` guard,
`template`/`parts` use the same single-step `as` the file already used internally (the
arch gate flags only `as unknown as` double escapes). Validation behavior unchanged,
proven by the new fail-closed step.
3. runtime.ts: `propertySink(el)` performs a one-step structural assertion to
`SpikePropertySink` (Element plus a string index signature over `unknown`) and both the
fresh-mode initial property write and the subscription write go through it. Semantics
preserved: fresh property initialization still happens, claim still performs zero
property writes (live value preserved), subsequent signal writes still land; the sink is
generic for any Element per the reviewer correction.

COMMANDS_AND_EXIT_CODES:

- `deno test -A packages/adapter-vite/__tests__/compiled-element-spike.test.ts` → exit=0
(ok | 4 passed (13 steps) | 0 failed)
- `deno test -A packages/element/__tests__/compiled-part-program-spike.test.ts` → exit=0
(ok | 2 passed (11 steps) | 0 failed)
- `deno lint` → exit=0 (Checked 723 files)
- `deno task arch:check` → exit=0 (Architecture contract check passed, 1765 tracked files)
- `deno task typecheck` → exit=0
- `deno task fmt:check` → exit=0 (Checked 1321 files)
- `deno test -A packages/adapter-vite/__tests__` (full adapter suite) → exit=0
(ok | 638 passed (71 steps) | 0 failed)
- `deno test --allow-read --allow-write --allow-env --allow-net --allow-run packages/element/__tests__` (full element suite) → exit=0 (ok | 294 passed (11 steps) | 0 failed)
- `deno task autoflow:ci` → first run exit=1 (FAIL fullstack:cloudflare-config-check:
transient npm registry error `error reading a body from connection` downloading js-yaml
during the wrangler dry-run; every other selected gate PASSed); retry → exit=0
(48/48 selected gates PASS, 0 FAIL)

RESIDUAL_RISKS:

- The first autoflow:ci run's cloudflare-config-check failure was a network flake, not a
product defect; the retry passed all 48 gates. If CI runners hit the same registry
instability, the flake may recur independently of this repair.
- `SpikePropertySink`'s index signature is an honest structural view (a compiled property
Part owns its named property by construction), but misspelled property names in future
grammar extensions would still compile; the frozen grammar in #1161 should type the
property-name union.
- No other residual risks introduced by this repair; prior-slice risks stand as recorded.

SCOPE_CONFIRMATION:
All edits are inside repair-2-owned paths; expected-program.json is the deterministic
expected-transform evidence whose update packet instruction 1 explicitly requires when
generated program bytes change. No public API/export/package configuration/version change;
no architecture allowlist entry, lint suppression, ignore directive, compatibility path,
workspace alias, private cross-package import, fallback renderer, or weakened assertion; no
production edits outside the two element compiled files; no dispatch/review/state/
governance/ADR/roadmap files touched. No commit, push, merge or GitHub update performed.

NEXT_REQUIRED_ACTION:
Release verifier re-review of a0-001-repair-2 against CI run 33071761401's two failed gates: re-run the
required commands above (or rely on a fresh CI run of the repaired branch), confirm both
gates now pass, and issue the GO/NO-GO decision for the #1160 alpha.0 evidence.
68 changes: 68 additions & 0 deletions docs/evidence/v0.44.0-agent-loops/a0-001/repair-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# a0-001 repair 2 — CI lint and architecture contract

```yaml
loopId: a0-001-repair-2
parentLoop: a0-001
kind: repair
candidate: 0.44.0-alpha.0
issue: 1160
failedCandidateSha: bcc99b25df5a2ded515995b8fb0e3470bf87258e
mergedSha: 4e1c322df76849846c8254946515e2575c758255
failedRun: 33071761401
implementerSession: session_5998df53-7c93-43ef-a13c-424763ba16cd
risk: critical
ownedPaths:
- packages/adapter-vite/__fixtures__/compiled-element-spike/counter.tsx
- packages/adapter-vite/__tests__/compiled-element-spike.test.ts
- packages/element/src/internal/compiled/program.ts
- packages/element/src/internal/compiled/runtime.ts
- packages/element/__tests__/compiled-part-program-spike.test.ts
- docs/evidence/v0.44.0-agent-loops/a0-001/repair-2-result.md
```

## CI facts

GitHub Actions run `33071761401` completed with exit code 1. Every selected gate passed
except these two deterministic failures:

1. `deno lint` rejected the fixture button at `counter.tsx:37` because it has no explicit
`type` attribute (`jsx-button-has-type`).
2. `deno task arch:check` rejected three production `as unknown as` escapes:
`program.ts:258`, `runtime.ts:322`, and `runtime.ts:327`.

The full packed, browser, runtime, coverage, build, consumer, example, and negative matrix
otherwise passed in that same run. This repair must address only the two failed gates.

## Required repair

1. Add an explicit non-submitting type to the fixture button and update deterministic
expected transform evidence/assertions only if the generated program bytes change.
2. Remove all three `as unknown as` production escapes using honest narrowing or a small,
local typed helper. Preserve runtime semantics, especially fresh property initialization,
claim preservation of live values, subsequent signal writes, and fail-closed program
validation.
3. Add or strengthen focused tests if needed to prove the replacement casts do not weaken
validation or DOM property writes. Do not add an architecture allowlist entry.
4. Record test-first RED/GREEN evidence and exact exit codes in `repair-2-result.md`.

## Forbidden changes

- No public API/export/package configuration/version changes.
- No architecture allowlist, lint suppression, ignore directive, hidden compatibility path,
workspace alias, private cross-package import, fallback renderer, or weakened assertion.
- No production edits outside the two element compiled files.
- No edits to dispatch, prior results/reviews, execution state, governance, ADR, or roadmap.
- Do not commit, push, merge, close the issue, or claim alpha.0 closure.

## Required commands

- focused adapter and element compiled-spike tests
- `deno lint`
- `deno task arch:check`
- `deno task typecheck`
- `deno task fmt:check`
- full adapter test suite
- full element test suite
- `deno task autoflow:ci`

Return `PASS`, `FAIL`, or `BLOCKED` with exact commands and exit codes.
53 changes: 53 additions & 0 deletions docs/evidence/v0.44.0-agent-loops/a0-001/review-repair-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Reviewer decision — a0-001 repair-2

```yaml
loopId: a0-001-repair-2
issue: 1160
baseSha: 4e1c322df76849846c8254946515e2575c758255
failedCiRun: 33071761401
decision: GO
scope: ci-repair-only
```

## Decision

GO. The repair removes exactly the lint and architecture-contract failures from the failed
candidate run without changing the public surface or narrowing property-sink semantics.
The initially proposed non-input restriction was rejected before acceptance and removed.

## Reviewed diff

- The fixture button receives the required non-submitting `type` attribute and the frozen
program/SSR evidence changes deterministically with it.
- Program validation returns a newly constructed validated value instead of laundering the
original input through a double assertion.
- DOM property writes use one local structural view while remaining generic for every
element target admitted by the compiled program.
- A focused negative step preserves fail-closed part-index and version validation.
- No public export, package configuration, allowlist, suppression, compatibility path,
workspace alias, private cross-package import, fallback renderer, or weakened assertion
was introduced.

## Independent verification

| Command | Exit | Result |
| ----------------------------------------- | ---: | ---------------------------- |
| focused adapter compiled-spike test | 0 | 4 passed, 13 steps |
| focused element compiled-spike test | 0 | 2 passed, 11 steps |
| `deno lint` | 0 | 723 files |
| `deno task arch:check` | 0 | architecture contract passed |
| `deno task typecheck` | 0 | package graph passed |
| `deno task fmt:check` | 0 | 1322 files |
| full adapter tests | 0 | 638 passed, 71 steps |
| full element tests | 0 | 294 passed, 11 steps |
| implementer `deno task autoflow:ci` retry | 0 | 48 selected gates passed |

The first full-matrix attempt failed only while downloading `js-yaml` from the npm registry
for the Wrangler dry run (`error reading a body from connection`). The unchanged retry
passed all 48 gates. This is retained as network-flake evidence, not treated as a product
failure or a reason to weaken the gate.

## Release-boundary note

This accepts the #1160 repair only. `alpha.0` is an internal integration baseline rather
than an independently published version, so this decision is not a release promotion.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class SpikeCounter extends OpenElement {
<div class='spike'>
<h1>Count: {this.count}</h1>
<input value={this.label} />
<button onClick={this.increment}>+</button>
<button type='button' onClick={this.increment}>+</button>
{this.count > 0 ? <p class='parity'>positive</p> : <p class='parity'>zero</p>}
<ul>{this.items.map((item) => <li key={item.id}>{item.text}</li>)}</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@
{
"k": "el",
"tag": "button",
"attrs": [],
"attrs": [
[
"type",
"button"
]
],
"children": [
{
"k": "text",
Expand Down
16 changes: 15 additions & 1 deletion packages/element/__tests__/compiled-part-program-spike.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ function build043Equivalent(doc: FDocument, state: FixtureState): FElement {
input.setAttribute('value', state.label);
div.appendChild(input);
const button = doc.createElement('button');
button.setAttribute('type', 'button');
button.appendChild(doc.createTextNode('+'));
div.appendChild(button);
div.appendChild(doc.createComment('oe:p3'));
Expand Down Expand Up @@ -467,7 +468,7 @@ Deno.test('compiled part program spike - one program, three execution modes', as
'<div class="spike">' +
'<h1>Count: <!--oe:p0-->0</h1>' +
'<input value="ready">' +
'<button>+</button>' +
'<button type="button">+</button>' +
'<!--oe:p3--><p class="parity">zero</p><!--oe:/p3-->' +
'<ul><!--oe:p4--><li>alpha</li><li>beta</li><!--oe:/p4--></ul>' +
'</div>',
Expand Down Expand Up @@ -637,6 +638,19 @@ Deno.test('compiled part program spike - one program, three execution modes', as
assertEquals(doc.counts.elements, 0);
});

await t.step('program validation still fails closed after the cast removal (repair-2)', () => {
const broken = JSON.parse(programJson);
broken.parts[0] = { ...broken.parts[0], index: 7 };
assertThrows(
() => programModule.validateSpikeProgram(broken),
Error,
'parts[0].index must equal its position',
);
const wrongVersion = JSON.parse(programJson);
wrongVersion.version = 2;
assertThrows(() => programModule.validateSpikeProgram(wrongVersion), Error, 'version');
});

await t.step('measurement evidence against the frozen 0.43-equivalent proxy', () => {
// 0.43-equivalent: full subtree re-allocation + full marker walk per update.
const proxyDoc = new FDocument();
Expand Down
7 changes: 6 additions & 1 deletion packages/element/src/internal/compiled/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,5 +255,10 @@ export function validateSpikeProgram(raw: unknown): PartProgramSpike {
}
}

return raw as unknown as PartProgramSpike;
return {
version: PART_PROGRAM_SPIKE_VERSION,
tag: raw.tag,
template: raw.template as SpikeTreeNode[],
parts,
};
}
19 changes: 17 additions & 2 deletions packages/element/src/internal/compiled/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,20 +311,35 @@ function resolvePath(root: Node, path: number[], where: string): Element {
return node;
}

/**
* Structural view of an Element carrying writable DOM properties. A compiled
* property Part owns the named property on its target element by construction,
* so this single-step structural assertion is honest: no double cast, no
* `unknown` laundering, and the sink stays generic for any Element.
*/
interface SpikePropertySink extends Element {
[property: string]: unknown;
}

function propertySink(el: Element): SpikePropertySink {
return el as SpikePropertySink;
}

/** Attach path-addressed prop/event parts. */
function attachPathParts(ctx: MountContext, root: Node, mode: 'fresh' | 'claim'): void {
for (const part of ctx.program.parts) {
if (part.k === 'prop') {
const el = resolvePath(root, part.path, 'prop part');
const sink = propertySink(el);
if (mode === 'fresh') {
const initial = signalOf(ctx, part.signal).value;
el.setAttribute(part.name, String(initial));
(el as unknown as Record<string, unknown>)[part.name] = initial;
sink[part.name] = initial;
}
// claim deliberately does not write the initial value: live DOM state
// (e.g. a user-edited input value) survives a successful claim.
subscribeWrites(ctx, part.signal, (value) => {
(el as unknown as Record<string, unknown>)[part.name] = value;
sink[part.name] = value;
});
continue;
}
Expand Down
Loading