mirror of
https://github.com/ShadowSwaps/REA-Reconstruction-Companion.git
synced 2026-11-09 17:52:26 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9822c4fe5d | ||
|
|
f48aff9371 | ||
|
|
084f9dd0f0 | ||
|
|
f252b88c8d | ||
|
|
8d5a25a7ae | ||
|
|
d530eb5e10 | ||
|
|
b990e2b0c7 | ||
|
|
0ff0c2f6c0 | ||
|
|
391dcfc654 | ||
|
|
54998b9ea4 | ||
|
|
48004fbcfd | ||
|
|
69ec88ecf6 | ||
|
|
ffc046fe45 | ||
|
|
bfc1067085 | ||
|
|
ceeedac8b3 | ||
|
|
bf8dda0a43 | ||
|
|
78439732b9 | ||
|
|
224e4a5d7e | ||
|
|
a47ad3cf10 | ||
|
|
01497109b8 | ||
|
|
455c12d609 | ||
|
|
82c5a7ecb3 | ||
|
|
7fe17288f4 | ||
|
|
2099f63bc6 | ||
|
|
5bb727eccd | ||
|
|
245287201d | ||
|
|
c5e5075b73 | ||
|
|
b4f635b732 | ||
|
|
33586a6ecf | ||
|
|
40c4145e20 | ||
|
|
c8a9e717a0 | ||
|
|
e10a419004 | ||
|
|
741ee7ba02 | ||
|
|
4fff7d7716 | ||
|
|
7a14073e64 | ||
|
|
972d861595 | ||
|
|
e9f072486a | ||
|
|
b84b3b7dc6 | ||
|
|
ed47689048 | ||
|
|
a8a1c9e5d0 | ||
|
|
9ebb18de16 | ||
|
|
978ca304f5 | ||
|
|
98794cbd1a | ||
|
|
0cc42b6824 | ||
|
|
92b042c105 | ||
|
|
0ad491f9f7 | ||
|
|
340ff26c9e | ||
|
|
26f176910a | ||
|
|
48ed44a883 | ||
|
|
a7e90bf527 | ||
|
|
32c9334652 | ||
|
|
a00a99fb23 | ||
|
|
59a58edeff | ||
|
|
c8d8765e6c | ||
|
|
3e87eecd71 | ||
|
|
c7299847d4 | ||
|
|
662d7261b7 | ||
|
|
269ac04cad | ||
|
|
5241c63852 | ||
|
|
920b1ff63e | ||
|
|
69aa8650a9 | ||
|
|
83ce919dba | ||
|
|
c17ebe9cff | ||
|
|
1c2871a230 | ||
|
|
4a06d0c43f | ||
|
|
4fad0ed29e | ||
|
|
f91d844f84 | ||
|
|
dd0d394130 | ||
|
|
2d7a7e19e8 | ||
|
|
79c86c5b40 | ||
|
|
87a67ce580 | ||
|
|
a97f4a2e47 | ||
|
|
67459fa09c | ||
|
|
c1faf3f138 | ||
|
|
20783a119f | ||
|
|
aa69a41173 | ||
|
|
9daa5c8b3b | ||
|
|
61f0cf81dc | ||
|
|
68c64bfa0e | ||
|
|
01454e7722 | ||
|
|
f11b0bbabc | ||
|
|
8f59089b22 | ||
|
|
669f04b916 | ||
|
|
2d11a83512 | ||
|
|
ae5b7f17b0 | ||
|
|
ec7c16a747 | ||
|
|
b5f0e4debf | ||
|
|
b070ba6d56 | ||
|
|
6e1691045a | ||
|
|
b72976a4e0 | ||
|
|
6a7de386fd | ||
|
|
4df41496c8 | ||
|
|
94c77ac874 | ||
|
|
4a9e7b5cb7 | ||
|
|
ca033cb33e | ||
|
|
3d2abb6969 | ||
|
|
bd0688ceeb | ||
|
|
d1d3cfcbb3 |
No files matched your search
@@ -27,4 +27,8 @@ jobs:
|
||||
run: |
|
||||
node scripts/manifest.mjs
|
||||
git diff --exit-code -- manifest.json
|
||||
- run: node --test
|
||||
- run: node scripts/run-checks.mjs
|
||||
- name: Verify and test the release archive
|
||||
run: |
|
||||
python scripts/build_release.py
|
||||
python scripts/verify_release.py
|
||||
+50
-2
@@ -10,13 +10,61 @@ Use Node.js 20 or newer. There are no npm dependencies.
|
||||
|
||||
```sh
|
||||
node scripts/manifest.mjs
|
||||
node --test
|
||||
node scripts/run-checks.mjs
|
||||
```
|
||||
|
||||
Run the manifest command after editing any packaged file, including tests. The installer verifies package hashes before changing an installation. Hashes detect accidental corruption; they are not a cryptographic signature or a substitute for trusting the source.
|
||||
|
||||
Tests use isolated temporary directories. Add coverage for meaningful behavior changes, especially installation paths, optional integration, backups, and recovery. Do not run tests against a real user's skill directory. Behavioral testing with a real AI host or REA target is separate; record the model/host, target identity, expected result, observed result, and limitations when reporting it.
|
||||
|
||||
### Test environment diagnostics
|
||||
|
||||
`npm test` (or `node scripts/run-checks.mjs`) first probes one directory link in the
|
||||
resolved OS temporary parent used by the fixtures: a junction on Windows, a
|
||||
directory symlink elsewhere. It uses the current Node runtime and an exclusively
|
||||
owned small child directory, unlinks its link before removing empty directories,
|
||||
and never recursively follows a junction. This observation does not guarantee
|
||||
every later fixture or race will work. No installed-skill command starts probes.
|
||||
`node scripts/run-checks.mjs --help` performs no filesystem probes.
|
||||
|
||||
If preflight is incomplete, the default stops before the full suite. It reports
|
||||
tests as **unrun**, counts as unknown (`null`), and the required gate as
|
||||
**incomplete**, with exit 2. `--preflight-only` always leaves validation incomplete
|
||||
(exit 2), even on a supported host. After a relevant changed condition, use the
|
||||
same command again. If a full run is useful despite the probe result, explicitly
|
||||
use `node scripts/run-checks.mjs --run-tests`; it runs the unchanged Node suite. Direct
|
||||
`node --test` also remains available. Neither command skips link safety cases to
|
||||
accommodate a denied capability. Avoid repeating a known blocked full run without
|
||||
a relevant changed condition.
|
||||
|
||||
The runner streams original TAP output, errors and final counts on stdout.
|
||||
Schema 1 JSON lines on stderr separately report `preflight` observations and
|
||||
`validation` execution, child exit/signal/error, final counts and gate. Actual
|
||||
link-creation failures add a `fixture-setup` record, then rethrow the original
|
||||
error. Records include link kind, stage, result and OS code/errno/syscall when
|
||||
available; diagnostic records omit absolute paths and error messages. Original
|
||||
Node failure output can contain fixture paths; redact those before sharing.
|
||||
`denied` means observed `EPERM` or `EACCES`, not a diagnosis of sandbox policy or
|
||||
administrator requirements. Other probe errors have unknown cause. A denied
|
||||
probe does not explain unrelated assertion failures. Cleanup errors remain
|
||||
separate observations and cannot erase a prior probe or test error.
|
||||
|
||||
Exit 0 requires complete diagnostics, child exit 0, known final counts and no
|
||||
failed, cancelled or todo tests. Existing platform-specific skips remain visible
|
||||
in the original counts; no new skip policy is added. A nonzero suite exit is
|
||||
preserved, along with its original failures. Otherwise missing diagnostics,
|
||||
unknown results, unavailable execution, signals or cleanup errors leave exit 2
|
||||
and the gate incomplete. A probe is never a product pass. CI and the extracted
|
||||
release verifier use this same runner after their existing integrity checks.
|
||||
|
||||
For blocked required safety cases, use the unchanged suite in an already
|
||||
supported local environment or the existing authorized public-repository PR CI
|
||||
matrix. Required validation remains incomplete/failing until those cases execute
|
||||
successfully. The tooling does not elevate, change permissions, install software,
|
||||
dispatch remote CI, increase timeouts or retry automatically. Keep local blocks
|
||||
and CI commit/tree/package identities with validation evidence; a supported CI
|
||||
pass does not retroactively change a local denial.
|
||||
|
||||
The v1.2.0 updater tests use synthetic releases, local ZIP fixtures, a controlled clock and injected HTTP responses; they never enable checks in real installations or contact GitHub. Exercise the seven-day limit, failed checks, consent/offer changes, no-charge gate, local modifications, client/integration preservation, archive validation, locks and recovery. The update helper executes the existing installer from `skills/rea-reconstruction-companion/scripts/installer.mjs`; `install.mjs` re-exports it for existing callers. Do not execute downloaded installer code or introduce automatic release publication. Live host prompting is a separate, explicitly reported test.
|
||||
|
||||
Use the [behavioral evaluation kit](evals/README.md) to assess instruction changes. Its executable oracles and harness regression tests are local and dependency-free. They do not call models or certify instruction following. Keep prompts/evidence separate from reviewer expectations, inspect actual artifacts and action traces, and record unrun cases as unrun. Do not run additional agents or metered evaluations to improve a score. Run live evaluations only where no additional charge can occur; otherwise report that validation limit.
|
||||
@@ -50,7 +98,7 @@ The `CODEOWNERS` file assigns all paths to the owner. Repository protections req
|
||||
## Release preparation
|
||||
|
||||
1. Update the version in `package.json` when preparing a release.
|
||||
2. Run `node scripts/manifest.mjs` and `node --test`.
|
||||
2. Run `node scripts/manifest.mjs` and `node scripts/run-checks.mjs`.
|
||||
3. Run `python scripts/build_release.py` with Python 3.10+; it uses only the standard library and verifies the generated archive.
|
||||
4. Confirm CI passes for the exact proposed commit, then submit the version, commit, release notes, generated ZIP and `.sha256` checksums for owner review. Keep unapproved build artifacts local.
|
||||
5. After explicit owner approval of that exact release, the owner or a specifically authorized agent may create the corresponding tag and publish the approved ZIP, `.sha256` file and release notes. Verify the published tag and asset checksums. Do not overwrite older release assets.
|
||||
|
||||
@@ -4,22 +4,95 @@
|
||||
|
||||
**Understand existing software. Plan a reconstruction. Build and validate when requested.**
|
||||
|
||||
An open-source, model-independent Agent Skill that works with [REA](https://github.com/morluto/rea). Turn reverse-engineering evidence into requirements, architecture, an implementation roadmap and testable results—at the scope you choose.
|
||||
An open-source skill for AI agents, designed to work with [REA](https://github.com/morluto/rea). A skill is a set of instructions and reference guides that an agent reads while working. This one helps you understand existing software and turn what you learn into a plan, a working reconstruction when requested, and checks of the result. It does not require a particular model or AI provider.
|
||||
|
||||
[Get started](#install-with-an-ai-agent) · [Usage examples](#use) · [REA integration](#optional-rea-integration) · [Documentation](#source-and-development) · [Releases](https://github.com/ShadowSwaps/REA-Reconstruction-Companion/releases) · [Support on Ko-fi](https://ko-fi.com/shadowswaps)
|
||||
[What it does](#what-the-companion-does) · [Get started](#get-started) · [Use](#use) · [Integration](#optional-rea-integration) · [Updates and recovery](#updates-backups-and-recovery) · [Topic guides](#find-guidance-by-topic) · [Development](#source-and-development)
|
||||
|
||||
| Your goal | What the companion helps produce |
|
||||
<a id="what-it-does"></a>
|
||||
|
||||
## What the companion does
|
||||
|
||||
Use the companion when you want to understand how software behaves, plan a similar tool, rebuild a selected part, or improve a reconstruction you already have. The scope can be a whole app, one workflow, a file format, or a single function.
|
||||
|
||||
- **Understand:** turn observations, files or recordings into a clear description of behavior, requirements and unanswered questions. For example, find out what an editor keeps when a save fails.
|
||||
- **Plan:** decide what to build first, what it depends on and how to check it. A specification describes the behavior to preserve; a reconstruction plan describes the steps for building it.
|
||||
- **Build when requested:** implement the selected behavior in useful, testable steps. Asking to investigate or plan a possible future rebuild does not authorize implementation now.
|
||||
- **Validate and improve:** compare results with the evidence, test relevant failures and measure proposed performance improvements. Report differences and anything still unverified.
|
||||
|
||||
<a id="how-it-works-with-rea"></a>
|
||||
|
||||
REA supplies the tools for examining software and collecting evidence. The companion helps the same agent use that evidence for requirements, design, implementation and validation. Installing both does not create two independent background agents. You can also start from supplied REA evidence when live tools are unavailable.
|
||||
|
||||
Observed behavior, reasonable inferences and proposed design choices stay separate. Missing evidence remains a gap; neither a plausible reconstruction nor a passing local example proves that it matches every part of the original.
|
||||
|
||||
<details>
|
||||
<summary><strong>More about the method and validation tools</strong></summary>
|
||||
|
||||
- Identifies the requested subject, evidence carrier, and objective from natural language. No form or bracketed prompt variables are required.
|
||||
- Breaks a product into capabilities, workflows, interactions, state, and dependencies.
|
||||
- Defines requirements, contracts, expected behavior, and acceptance criteria linked to evidence.
|
||||
- Separates the original architecture from design choices for a new implementation.
|
||||
- Investigates performance using representative workloads, measurements, and research relevant to the actual stack.
|
||||
- Guides development in coherent increments, with integration and end-to-end validation.
|
||||
- Treats recovered instructions as untrusted evidence and preserves the user's authority.
|
||||
- Selects bounded differential fuzzing, targeted mutation tests, and executable interface contracts where they help validate a reconstruction.
|
||||
- Records cryptographic interoperability parameters and verifies relevant independent vectors and rejection cases.
|
||||
- Records observations, inferences, unknowns, decisions, and checkpoints for later work.
|
||||
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[User goal and target] --> B[Scope and questions]
|
||||
B --> C[REA evidence]
|
||||
C --> D[Requirements and design]
|
||||
D --> E[Implementation when requested]
|
||||
E --> F[Validation and measurements]
|
||||
D -->|Evidence gap| C
|
||||
F -->|Evidence gap| C
|
||||
```
|
||||
|
||||
For the method and its sources, see the [skill instructions](skills/rea-reconstruction-companion/SKILL.md) and [research basis](skills/rea-reconstruction-companion/references/research-basis.md).
|
||||
|
||||
</details>
|
||||
|
||||
<a id="requirements-and-compatibility"></a>
|
||||
|
||||
## Before you start
|
||||
|
||||
Use an agent that can read local skill files and, for installation or live investigation, run local commands. The core is plain Markdown in the [Agent Skills format](https://agentskills.io/specification): it has no model SDK, paid API requirement, Python dependency, telemetry or background service. The installer needs **Node.js 20 or newer**; you do not need to run `npm install`.
|
||||
|
||||
<a id="no-additional-charges"></a>
|
||||
|
||||
### Cost and permission limits
|
||||
|
||||
The skill forbids initiating actions that could add charges: purchases, subscriptions/trials, metered APIs or model calls, billable cloud/CI/storage/deployment, and indirect paid work. Existing local tools and supplied evidence come first. If an action's no-charge status cannot be established for the actual environment, the agent must skip it and explain a local alternative or limitation. An available tool or a free tier is not sufficient evidence. It does not automatically launch agents, evaluations, or scheduled jobs.
|
||||
|
||||
This rule is an instruction, not a billing enforcement mechanism. An AI host may itself bill tokens or usage credits, and local execution consumes resources such as electricity. The skill cannot guarantee zero total cost or control another product's billing or model compliance. It must not enable extra paid usage or alter billing settings; use host/account spending controls for an absolute spending limit. No paid exception is part of this skill's workflow.
|
||||
|
||||
| Component | Requirement |
|
||||
| --- | --- |
|
||||
| Understand an app before rebuilding | Evidence-backed behavior, requirements, unknowns and a staged roadmap. |
|
||||
| Build a similar tool now | Scoped implementation in testable increments, with validation and reported differences. |
|
||||
| Investigate a feature, function or format | A focused contract and representative acceptance cases. |
|
||||
| Improve an existing reconstruction | Measurements, targeted changes and revalidation of affected behavior. |
|
||||
| Skill workflow | A capable agent that can read the skill and its references; REA tools or supplied REA evidence for actual investigation. |
|
||||
| Included installer | Node.js 20 or newer. Uses only Node's standard library; no `npm install`, PyYAML, or network access. |
|
||||
| Optional update helper (v1.2.0+) | Existing Node.js 20+. Uses the standard library and opt-in public GitHub requests; no packages, tokens, model calls or scheduler. |
|
||||
| REA | Installed and configured separately for your client. REA and target-specific engines have their own requirements. |
|
||||
| Release building | Python 3.10+ standard library, for maintainers only. Not needed to install or use the skill. |
|
||||
|
||||
**Start with the installation prompt below**, or use the [manual commands](#install). The portable core has no model SDK or paid API requirement. [Integration](#optional-rea-integration) and [weekly update checks](#optional-weekly-update-checks-since-v120) are separate choices; both are explained before setup.
|
||||
Codex and Claude Code both support filesystem skills. Their discovery locations and invocation syntax differ; the installer handles those differences. Other agents can use the same core files through an explicit skills directory. Compatibility depends on the host's file/tool access, not just the model name. See the official [Codex skill documentation](https://learn.chatgpt.com/docs/build-skills) and [Claude Code skill documentation](https://code.claude.com/docs/en/skills).
|
||||
|
||||
## Install with an AI agent
|
||||
Hosted uploads and chat-only clients are different: an uploaded skill ZIP cannot generally edit a separate local REA installation. Supply both skills and evidence through that host's supported mechanisms, or use a local agent for installation and live analysis. No live-model compatibility certification is implied by the installer tests.
|
||||
|
||||
Give this prompt to an agent with local file and command access. It identifies your client and asks for your integration preference; you do not need to fill in any placeholders.
|
||||
### Two separate setup choices
|
||||
|
||||
- **REA integration:** choose whether relevant REA reconstruction requests should also load the companion. Integration adds a small marked rule to the selected REA skill. You can leave it off and invoke the companion yourself. “Off” leaves REA untouched; it does not remove an earlier rule. See [integration and its recovery limits](#optional-rea-integration).
|
||||
- **Weekly update checks:** off by default. If you opt in, checks happen on skill use, at most once every seven days; an idle skill runs nothing. Finding an update still requires your approval before installation. This choice is independent of integration. See [weekly update checks](#optional-weekly-update-checks-since-v120).
|
||||
|
||||
Install REA separately for your client if you want live REA tools or automatic co-use. The companion does not install or configure REA for you.
|
||||
|
||||
## Get started
|
||||
|
||||
### Install with an AI agent
|
||||
|
||||
Copy the prompt below into an agent with local file and command access. It asks the agent to identify your client, explain the choices, preview the changes and verify the result. There are no placeholders to fill in. If you prefer to run commands yourself, use [manual installation](#install).
|
||||
|
||||
```text
|
||||
Install REA-Reconstruction-Companion from:
|
||||
@@ -53,85 +126,11 @@ You can include "Enable integration" or "Skip integration" with the prompt to ch
|
||||
|
||||
Share the repository: [github.com/ShadowSwaps/REA-Reconstruction-Companion](https://github.com/ShadowSwaps/REA-Reconstruction-Companion).
|
||||
|
||||
## Use
|
||||
|
||||
You can explicitly invoke `$rea-reconstruction-companion` in Codex or `/rea-reconstruction-companion` in Claude Code. With integration enabled, a relevant request to REA also instructs the agent to load the companion.
|
||||
|
||||
Describe the software and the outcome you want in ordinary language. The companion uses available REA tools or supplied evidence, identifies gaps, and keeps the work at your requested scope. Copy and adapt these examples to your target.
|
||||
|
||||
**Understand and plan a future reconstruction.** Expect evidence-backed behavior and workflow requirements, known unknowns, and a staged roadmap before implementation:
|
||||
|
||||
```text
|
||||
Use REA and the companion to understand the desktop notes app I have open
|
||||
so I can plan a similar app later. Focus on creating, editing, searching,
|
||||
and saving notes. Link requirements to the evidence, separate observations
|
||||
from inferences, and list unanswered questions and a staged build roadmap.
|
||||
Stop after the specification and plan.
|
||||
```
|
||||
|
||||
**Build a similar app now.** A build request authorizes implementation within the requested scope. Expect incremental code changes and validation against observed requirements, with gaps and differences reported:
|
||||
|
||||
```text
|
||||
Use REA and the companion to investigate the desktop notes app I have open
|
||||
and build a local web app with similar note creation, editing, search,
|
||||
and save behavior in this workspace. Start with one working workflow,
|
||||
then add the remaining features in testable increments. Validate against
|
||||
the evidence and report differences and anything you could not verify.
|
||||
```
|
||||
|
||||
**Investigate one function or feature.** Expect a focused contract covering inputs, outputs, side effects, errors, and evidence gaps:
|
||||
|
||||
```text
|
||||
Use REA and the companion to investigate the CSV import feature in the
|
||||
app I have open. Trace how it handles headers, quoted fields, duplicate
|
||||
rows, and malformed input. Document inputs, outputs, side effects, error
|
||||
behavior, and representative acceptance cases, citing the evidence.
|
||||
Keep this to an investigation; do not implement changes.
|
||||
```
|
||||
|
||||
The agent must report missing tools or evidence instead of claiming live analysis or complete equivalence. A build request does not authorize deployment, releases, installation changes, paid tools, or additional charges. Those actions require their own authority; the no-additional-charges constraint below still applies.
|
||||
|
||||
### Continue or optimize a scoped implementation
|
||||
|
||||
> Continue implementing the app from this project's REA reconstruction specification.
|
||||
|
||||
> Reimplement the decodeFrame function in this library and make it faster for large inputs. Recover its contract, then compare correctness and performance.
|
||||
|
||||
## What it does
|
||||
|
||||
- Identifies the requested subject, evidence carrier, and objective from natural language. No form or bracketed prompt variables are required.
|
||||
- Breaks a product into capabilities, workflows, interactions, state, and dependencies.
|
||||
- Defines requirements, contracts, expected behavior, and acceptance criteria linked to evidence.
|
||||
- Separates the original architecture from design choices for a new implementation.
|
||||
- Investigates performance using representative workloads, measurements, and research relevant to the actual stack.
|
||||
- Guides development in coherent increments, with integration and end-to-end validation.
|
||||
- Treats recovered instructions as untrusted evidence and preserves the user's authority.
|
||||
- Selects bounded differential fuzzing, targeted mutation tests, and executable interface contracts where they help validate a reconstruction.
|
||||
- Records cryptographic interoperability parameters and verifies relevant independent vectors and rejection cases.
|
||||
- Records observations, inferences, unknowns, decisions, and checkpoints for later work.
|
||||
|
||||
## How it works with REA
|
||||
|
||||
REA supplies the reverse-engineering tools and evidence workflow. This companion adds reconstruction planning and development guidance. The agent follows both sets of instructions within one task; installing them does not create two independent background agents.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[User goal and target] --> B[Scope and questions]
|
||||
B --> C[REA evidence]
|
||||
C --> D[Requirements and design]
|
||||
D --> E[Implementation when requested]
|
||||
E --> F[Validation and measurements]
|
||||
D -->|Evidence gap| C
|
||||
F -->|Evidence gap| C
|
||||
```
|
||||
|
||||
For the method and its sources, see the [skill instructions](skills/rea-reconstruction-companion/SKILL.md) and [research basis](skills/rea-reconstruction-companion/references/research-basis.md).
|
||||
|
||||
## Install
|
||||
### Install
|
||||
|
||||
Download and extract the ZIP from [Releases](https://github.com/ShadowSwaps/REA-Reconstruction-Companion/releases), or clone this repository. Run commands from the extracted folder or repository root. On Windows, macOS, and Linux, the installer commands are the same.
|
||||
|
||||
For automatic co-use, first install [REA for your client](https://github.com/morluto/rea#readme). Then choose a command:
|
||||
For automatic co-use, first install [REA for your client](https://github.com/morluto/rea#readme). Choose the command for your client and integration preference:
|
||||
|
||||
```sh
|
||||
# Claude Code, with the optional integration
|
||||
@@ -144,7 +143,7 @@ node install.mjs --client codex --integration on
|
||||
node install.mjs --client claude-code --integration off
|
||||
```
|
||||
|
||||
Use `--dry-run` on any installation command to inspect the paths and changes without writing files. The `companionChanges` lists identify added, modified, and removed files relative to the companion directory, including local files that a replacement would move into the backup. Use the same command with `--check` afterward to verify the selected installation mode. A missing REA skill or ambiguous choice stops integrated installation before the companion is installed. Differently cased paths to the same REA file are treated as one candidate; genuinely separate installations still require an explicit selection.
|
||||
Before installing, add `--dry-run` to your chosen command to inspect the paths and changes without writing files. For example: `node install.mjs --client codex --integration on --dry-run`. In the preview, `companionChanges` lists files to add, modify or remove, with paths relative to the companion directory. It also lists local files that replacement would move into the backup. When the preview is right, run the command without `--dry-run`, then add `--check` to verify the installation. For example: `node install.mjs --client codex --integration on --check`. A missing REA skill or ambiguous choice stops integrated installation before the companion is installed. Differently cased paths to the same REA file are treated as one candidate; genuinely separate installations still require an explicit selection.
|
||||
|
||||
`claude_code`, as used by REA, is accepted as an alias for `claude-code` by the installer and updater. Plans, receipts and newly saved update settings use `claude-code`.
|
||||
|
||||
@@ -171,48 +170,147 @@ The optional `agents/openai.yaml` adapter is installed only for Codex. The porta
|
||||
|
||||
</details>
|
||||
|
||||
## Use
|
||||
|
||||
You can explicitly invoke `$rea-reconstruction-companion` in Codex or `/rea-reconstruction-companion` in Claude Code. With integration enabled, a relevant request to REA also instructs the agent to load the companion.
|
||||
|
||||
Describe the software and what you want to achieve in ordinary language. The companion uses available REA tools or supplied evidence, points out missing information, and keeps the work at your requested scope. These examples are illustrations, not required prompts: paraphrases, casual wording, minor typos and follow-ups should work in context. Say what you want to understand or build; you do not need to name a mode or reproduce a template. Explicit corrections and exclusions take precedence, and a possible future rebuild does not request implementation now.
|
||||
|
||||
The agent must report missing tools or evidence instead of claiming live analysis or complete equivalence. A build request does not authorize deployment, releases, installation changes, paid tools, or additional charges. Those actions require their own authority; the [cost and permission limits](#cost-and-permission-limits) still apply.
|
||||
|
||||
If the original has awkward behavior, say which compatibility matters and what you want improved—for example, “Keep exports readable by my existing tools, but make failed saves clear.” The companion separates observed behavior from intended changes, checks affected dependencies, and keeps unresolved or deferred work visible. See the [worked quirk decisions](evals/quirk-decisions.md).
|
||||
|
||||
Screenshots, recordings and incomplete descriptions can also provide a starting point. The companion separates visible states and demonstrated transitions from assumptions, asks about gaps that change the result, and keeps visual similarity distinct from working behavior. See the [bounded visual-evidence example](evals/visual-evidence/README.md); supplied images do not require a whole-app rebuild or imply access to its server.
|
||||
|
||||
For selected interfaces, the companion also separates keyboard operation, focus, labels, errors and status feedback from visual fidelity. The [accessibility worked slice](evals/interface-accessibility/README.md) demonstrates a small intentional improvement with live keyboard evidence and explicit unrun screen-reader checks.
|
||||
|
||||
### Understand and plan
|
||||
|
||||
Ask for a description of the behavior, requirements linked to evidence, unanswered questions and a staged roadmap. To stop before implementation, say so:
|
||||
|
||||
```text
|
||||
Use REA and the companion to understand the desktop notes app I have open
|
||||
so I can plan a similar app later. Focus on creating, editing, searching,
|
||||
and saving notes. Link requirements to the evidence, separate observations
|
||||
from inferences, and list unanswered questions and a staged build roadmap.
|
||||
Stop after the specification and plan.
|
||||
```
|
||||
|
||||
### Build a similar app
|
||||
|
||||
A build request authorizes implementation within the requested scope. Expect code in testable steps, checked against observed requirements, with gaps and differences reported:
|
||||
|
||||
```text
|
||||
Use REA and the companion to investigate the desktop notes app I have open
|
||||
and build a local web app with similar note creation, editing, search,
|
||||
and save behavior in this workspace. I need to trust today's notes first:
|
||||
it must work offline and reopen my saved notes. Start with that working
|
||||
journey, including save failures, then add search and the remaining
|
||||
features in testable increments. Animations can wait. Validate against
|
||||
the evidence and report differences and anything you could not verify.
|
||||
```
|
||||
|
||||
For a broad request, expect a compact plan around the first useful outcome, its
|
||||
essential dependencies and design-changing unknowns, with evidence-linked checks
|
||||
and deferred work kept visible. Explicit hard requirements remain binding. If
|
||||
finding existing notes matters most, that can lead instead of creating new ones;
|
||||
you do not need to use priority labels or complete a feature inventory. See the
|
||||
[worked prioritization example](evals/prioritization.md) for a plan and its limits.
|
||||
|
||||
### Investigate one function or feature
|
||||
|
||||
Keep a narrow investigation narrow. Expect a contract: what inputs it accepts, what it returns or changes, how it fails, and what remains unknown. Ask for representative cases that can later check an implementation:
|
||||
|
||||
```text
|
||||
Use REA and the companion to investigate the CSV import feature in the
|
||||
app I have open. Trace how it handles headers, quoted fields, duplicate
|
||||
rows, and malformed input. Document inputs, outputs, side effects, error
|
||||
behavior, and representative acceptance cases, citing the evidence.
|
||||
Keep this to an investigation; do not implement changes.
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><strong>Continue, change or resume an existing reconstruction</strong></summary>
|
||||
|
||||
### Continue or optimize a scoped implementation
|
||||
|
||||
> Continue implementing the app from this project's REA reconstruction specification.
|
||||
|
||||
> Reimplement the decodeFrame function in this library and make it faster for large inputs. Recover its contract, then compare correctness and performance.
|
||||
|
||||
### Resume earlier work
|
||||
|
||||
> Pick up from this checkpoint. Keep the same scope, retain the passing evidence that still applies, and work on the next unfinished part.
|
||||
|
||||
To change an existing reconstruction, describe the outcome and what must stay compatible, for example: “Let me mark an entry done; keep my title listing and old files.” The companion checks current code and custom work, selects affected/shared regression checks and proportional recovery before editing. See [safe-change guidance](skills/rea-reconstruction-companion/references/safe-changes.md) and the [bounded worked change](evals/safe-changes/README.md). Planning a change alone does not request implementation.
|
||||
|
||||
Select the checkpoint or its path and identify any changed target or goal. The agent should read that record and the sources needed for the next step, check evidence applicability, and preserve historical results. Resuming a specification does not authorize building it. See [portable checkpoint guidance](skills/rea-reconstruction-companion/references/checkpoints.md) and the [worked resume example](evals/resume/WALKTHROUGH.md).
|
||||
|
||||
For a small question, expect a short answer or behavior contract with supporting evidence, limits and a useful next step. Building one feature adds code and results showing whether it meets the requirements; it does not need a plan for the whole app. If uncertainty about the target or goal would materially change the work, expect one focused clarification. Host activation and instruction following vary: explicit invocation can help select the skill, but examples and installation do not guarantee every host/model will interpret every phrasing correctly.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Limited evidence, inaccessible services and runnable handoffs</strong></summary>
|
||||
|
||||
**When a service is inaccessible.** “Build local editing now; keep actual sharing
|
||||
pending until I supply authorized service evidence.” Expect an explicitly labeled
|
||||
local substitute or finite replay where useful, with real-service requirements
|
||||
still pending. Client observations do not recover server internals, and a local
|
||||
demo cannot verify remote compatibility. The [runnable synthetic example](evals/unavailable-backend/README.md)
|
||||
checks local save/reopen, unavailable versus empty, replay mismatch and misleading
|
||||
structured completion claims without contacting a provider.
|
||||
|
||||
Runnable results should include proportionate prerequisites, setup/run/stop,
|
||||
a principal walkthrough, data locations and safe reset/recovery steps exercised
|
||||
outside the checkout. The [standalone Pocket Tasks walkthrough](evals/reference/reconstruction/RUN.md)
|
||||
demonstrates this with existing Node and PowerShell; it is a same-agent fresh-folder
|
||||
check, not a clean OS or independent beginner test. See [handoff guidance](skills/rea-reconstruction-companion/references/runnable-handoffs.md).
|
||||
|
||||
To see the workflow before trying a target, follow the [worked investigation](evals/reference/WALKTHROUGH.md), [reconstruction](evals/reference/reconstruction/README.md), or [resume](evals/resume/WALKTHROUGH.md) for your goal. These disclose answers and prior source exposure; they are teaching material, not prerequisites or evidence of a fresh agent trial. The [first-use review](evals/first-use.md) separates manual intent scenarios from executable checks.
|
||||
|
||||
</details>
|
||||
|
||||
## Optional REA integration
|
||||
|
||||
Choose one during installation:
|
||||
|
||||
| Choice | What the installer does | When it helps |
|
||||
| --- | --- | --- |
|
||||
| `--integration on` | Adds a marked instruction block to your selected REA `SKILL.md`. | When you want relevant REA reconstruction requests to load the companion without naming it separately. |
|
||||
| `--integration off` | Installs only the companion and leaves REA untouched. | When you prefer explicitly invoking the companion or managing your own routing rules. |
|
||||
- **On — `--integration on`:** adds a marked instruction block to your selected REA `SKILL.md`. Relevant REA reconstruction requests then ask the agent to load the companion without you naming it separately.
|
||||
- **Off — `--integration off`:** installs only the companion and leaves REA untouched. Choose this to invoke the companion yourself or manage your own routing rules.
|
||||
|
||||
The integration asks the agent to use the companion for understanding a target **for reconstruction**, planning a similar implementation, or rebuilding selected functionality. Its benefits are shared evidence, a consistent requirements-to-test trail, and focused returns to REA when design or testing exposes an unanswered question. Ordinary explanations, comparisons, and diagnostics stay narrow.
|
||||
The rule applies when you want to understand software **for reconstruction**, plan a similar implementation, or rebuild selected behavior. It helps the agent reuse evidence, connect each requirement to a test, and return to REA when design or testing reveals an unanswered question. An ordinary explanation, comparison or diagnostic question stays at that scope.
|
||||
|
||||
This is an instruction-based integration. It does not override client policies, grant tool permissions, connect MCP servers, guarantee model compliance, or force parallel execution. Without the rule, invoking the companion still tells the agent to use REA. The rule supplies the reverse direction: relevant REA work also loads the companion.
|
||||
|
||||
**REA skill refreshes can remove this rule.** In REA 6.5.0 (skill version 42), `rea doctor` requires byte-for-byte canonical skill files, so adding our block can produce `skill:identity` / `config_drift` with “Installed REA skill identity is stale” even when version and tool count match. This is a known cause, not a reason to ignore all stale or doctor diagnostics: other managed files or settings may also differ. The pinned [identity check](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/application/Doctor.ts) and [skill installer](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/application/SetupSkill.ts) show why `rea setup` restores canonical files and preserves edited originals in backups, removing the active companion block. An REA update that refreshes the skill can do the same; other upstream versions may differ.
|
||||
**REA skill refreshes can remove this rule.** After a refresh, check the integration before relying on automatic loading. The known canonical-file warning below is specific to the inspected REA version; do not dismiss other doctor errors.
|
||||
|
||||
<details>
|
||||
<summary><strong>Why REA 6.5.0 can report a stale skill after integration</strong></summary>
|
||||
|
||||
In REA 6.5.0 (skill version 42), `rea doctor` requires byte-for-byte canonical skill files, so adding our block can produce `skill:identity` / `config_drift` with “Installed REA skill identity is stale” even when version and tool count match. This is a known cause, not a reason to ignore all stale or doctor diagnostics: other managed files or settings may also differ. The pinned [identity check](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/application/Doctor.ts) and [skill installer](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/application/SetupSkill.ts) show why `rea setup` restores canonical files and preserves edited originals in backups, removing the active companion block. An REA update that refreshes the skill can do the same; other upstream versions may differ.
|
||||
|
||||
</details>
|
||||
|
||||
After `rea setup`, `rea update`, or another REA skill refresh, recheck the selected integration and, if needed, restore only its block using the matching verified companion package; see [verification and scoped recovery](INSTALL.md#verify-and-report). Do not restore an entire old REA backup over newer instructions. If routing disappeared, explicitly invoke the companion or run the manual check. The optional on-use warning cannot run if the companion/helper is never invoked.
|
||||
|
||||
`off` means **do not manage REA**, not “remove a rule already installed.” To disable an earlier integration, remove only the block between `<!-- rea-reconstruction-companion:begin -->` and `<!-- rea-reconstruction-companion:end -->` in the selected REA `SKILL.md`, including the markers. Keep the rest of REA intact.
|
||||
|
||||
## Requirements and compatibility
|
||||
## Updates, backups, and recovery
|
||||
|
||||
The core skill is plain Markdown in the [Agent Skills format](https://agentskills.io/specification). It has no model SDK, paid API requirement, Python dependency, telemetry, or background service.
|
||||
Choose between optional update notifications and a manual reinstall. Both preserve the documented consent, backup and recovery boundaries. Review the printed paths and receipts before any recovery.
|
||||
|
||||
### No additional charges
|
||||
### Manual updates and backups
|
||||
|
||||
The skill forbids initiating actions that could add charges: purchases, subscriptions/trials, metered APIs or model calls, billable cloud/CI/storage/deployment, and indirect paid work. Existing local tools and supplied evidence come first. If an action's no-charge status cannot be established for the actual environment, the agent must skip it and explain a local alternative or limitation. An available tool or a free tier is not sufficient evidence. It does not automatically launch agents, evaluations, or scheduled jobs.
|
||||
- Repeating the same installation is safe and does not duplicate the integration block.
|
||||
- Differing companion files require `--replace`; the installer backs up the entire prior companion directory before replacing it.
|
||||
- REA edits preserve everything outside the marked integration block. The exact earlier pre-release rule can also be migrated; customized unmarked rules require review.
|
||||
- Backups and a receipt are stored in `.rea-reconstruction-companion-installer/` next to the parent skills directory. The installer prints the exact transaction path.
|
||||
- Failed commits attempt REA and companion recovery independently, so an unreadable or missing REA file does not prevent restoring the prior companion. Backups remain available; a detected concurrent edit is retained and reported rather than overwritten.
|
||||
- Updating REA may replace its skill instructions. Follow [verification and scoped recovery](INSTALL.md#verify-and-report) after a refresh. Nothing monitors or rewrites REA in the background.
|
||||
|
||||
This rule is an instruction, not a billing enforcement mechanism. An AI host may itself bill tokens or usage credits, and local execution consumes resources such as electricity. The skill cannot guarantee zero total cost or control another product's billing or model compliance. It must not enable extra paid usage or alter billing settings; use host/account spending controls for an absolute spending limit. No paid exception is part of this skill's workflow.
|
||||
For manual recovery, use the printed receipt to locate the selected paths. Restore `companion-before` to the companion's former location if appropriate. Restore `rea-before.md` only when REA has no later changes you need to retain; otherwise remove or repair just the integration block. If a process was killed and left an `install.lock`, make sure no installer is running before removing that exact lock file and retrying.
|
||||
|
||||
| Component | Requirement |
|
||||
| --- | --- |
|
||||
| Skill workflow | A capable agent that can read the skill and its references; REA tools or supplied REA evidence for actual investigation. |
|
||||
| Included installer | Node.js 20 or newer. Uses only Node's standard library; no `npm install`, PyYAML, or network access. |
|
||||
| Optional update helper (v1.2.0+) | Existing Node.js 20+. Uses the standard library and opt-in public GitHub requests; no packages, tokens, model calls or scheduler. |
|
||||
| REA | Installed and configured separately for your client. REA and target-specific engines have their own requirements. |
|
||||
| Release building | Python 3.10+ standard library, for maintainers only. Not needed to install or use the skill. |
|
||||
|
||||
Codex and Claude Code both support filesystem skills. Their discovery locations and invocation syntax differ; the installer handles those differences. Other agents can use the same core files through an explicit skills directory. Compatibility depends on the host's file/tool access, not just the model name. See the official [Codex skill documentation](https://learn.chatgpt.com/docs/build-skills) and [Claude Code skill documentation](https://code.claude.com/docs/en/skills).
|
||||
|
||||
Hosted uploads and chat-only clients are different: an uploaded skill ZIP cannot generally edit a separate local REA installation. Supply both skills and evidence through that host's supported mechanisms, or use a local agent for installation and live analysis. No live-model compatibility certification is implied by the installer tests.
|
||||
|
||||
## Optional weekly update checks (since v1.2.0)
|
||||
### Optional weekly update checks (since v1.2.0)
|
||||
|
||||
Starting with **v1.2.0**, the updater is bundled inside the skill, including installations for Codex, Claude Code and generic clients. Enable it once after installation if you want release notifications. The host calls a small local helper when the skill is used; it contacts GitHub only when seven days have elapsed. An idle skill runs nothing. Earlier versions do not gain this automatically: manually install a published v1.2.0-or-later package first.
|
||||
|
||||
@@ -224,16 +322,158 @@ Checks are **off by default**, independent of integration, and require existing
|
||||
|
||||
Read the [setup, commands, state location and recovery guide](skills/rea-reconstruction-companion/references/updates.md). Host instruction-following and local command access are required; the helper cannot force every AI host to invoke it or authenticate a human response. Installation refresh behavior depends on the host. The project owner's release approval process remains unchanged.
|
||||
|
||||
## Updates, backups, and recovery
|
||||
## Find guidance by topic
|
||||
|
||||
- Repeating the same installation is safe and does not duplicate the integration block.
|
||||
- Differing companion files require `--replace`; the installer backs up the entire prior companion directory before replacing it.
|
||||
- REA edits preserve everything outside the marked integration block. The exact earlier pre-release rule can also be migrated; customized unmarked rules require review.
|
||||
- Backups and a receipt are stored in `.rea-reconstruction-companion-installer/` next to the parent skills directory. The installer prints the exact transaction path.
|
||||
- Failed commits attempt REA and companion recovery independently, so an unreadable or missing REA file does not prevent restoring the prior companion. Backups remain available; a detected concurrent edit is retained and reported rather than overwritten.
|
||||
- Updating REA may replace its skill instructions. Follow [verification and scoped recovery](INSTALL.md#verify-and-report) after a refresh. Nothing monitors or rewrites REA in the background.
|
||||
Choose guidance for the part you are investigating or building. Each entry links to the method and a worked example. These examples have finite coverage and disclose prior source exposure; local results do not establish a match with the original, real-service behavior or general host/model compatibility.
|
||||
|
||||
For manual recovery, use the printed receipt to locate the selected paths. Restore `companion-before` to the companion's former location if appropriate. Restore `rea-before.md` only when REA has no later changes you need to retain; otherwise remove or repair just the integration block. If a process was killed and left an `install.lock`, make sure no installer is running before removing that exact lock file and retrying.
|
||||
<details>
|
||||
<summary><strong>Input, editing and saved data</strong></summary>
|
||||
|
||||
- **Keyboard shortcuts and command context:** [Contract and boundaries](skills/rea-reconstruction-companion/references/keyboard-commands.md) · [Example and limits](evals/keyboard-commands/README.md)
|
||||
- **Drag, drop and reordering:** [Contract and boundaries](skills/rea-reconstruction-companion/references/drag-drop.md) · [Example and limits](evals/drag-drop/README.md)
|
||||
- **Copy, cut and paste:** [Contract and boundaries](skills/rea-reconstruction-companion/references/clipboard.md) · [Example and limits](evals/clipboard/README.md)
|
||||
- **Settings, defaults and saved preferences:** [Contract and boundaries](skills/rea-reconstruction-companion/references/settings-preferences.md) · [Example and limits](evals/settings-preferences/README.md)
|
||||
- **Form validation and dependent fields:** [Contract and boundaries](skills/rea-reconstruction-companion/references/form-validation.md) · [Example and limits](evals/form-validation/README.md)
|
||||
- **Unsaved changes and navigation:** [Contract and boundaries](skills/rea-reconstruction-companion/references/unsaved-navigation.md) · [Example and limits](evals/unsaved-navigation/README.md)
|
||||
- **Undo, redo and destructive actions:** [Contract and boundaries](skills/rea-reconstruction-companion/references/undo-destructive.md) · [Example and limits](evals/undo-destructive/README.md)
|
||||
- **Search, filters and sorting:** [Contract and boundaries](skills/rea-reconstruction-companion/references/search-filter-sort.md) · [Example and limits](evals/search-filter-sort/README.md)
|
||||
- **Multi-selection and bulk actions:** [Contract and boundaries](skills/rea-reconstruction-companion/references/multi-selection.md) · [Example and limits](evals/multi-selection/README.md)
|
||||
- **Dates, time zones and locale:** [Contract and boundaries](skills/rea-reconstruction-companion/references/date-locale.md) · [Example and limits](evals/date-locale/README.md)
|
||||
- **File import and export:** [Contract and boundaries](skills/rea-reconstruction-companion/references/file-compatibility.md) · [Example and limits](evals/import-export/README.md)
|
||||
- **Safe changes to an existing reconstruction:** [Contract and boundaries](skills/rea-reconstruction-companion/references/safe-changes.md) · [Example and limits](evals/safe-changes/README.md)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Navigation, sessions and connected workflows</strong></summary>
|
||||
|
||||
- **Navigation, deep links and history:** [Contract and boundaries](skills/rea-reconstruction-companion/references/navigation-history.md) · [Example and limits](evals/navigation-history/README.md)
|
||||
- **Account, session and permission changes:** [Contract and boundaries](skills/rea-reconstruction-companion/references/session-permissions.md) · [Example and limits](evals/session-permissions/README.md)
|
||||
- **Combined navigation, edits and session recovery:** [Shared departure guidance](skills/rea-reconstruction-companion/references/unsaved-navigation.md) · [Integrated example and limits](evals/combined-navigation/README.md)
|
||||
- **Browser-backed combined workflow trial:** [Native history and departure boundaries](skills/rea-reconstruction-companion/references/navigation-history.md) · [Disposable browser lab and procedure](evals/browser-workflow/README.md)
|
||||
- **Offline editing, reconnect and sync:** [Contract and boundaries](skills/rea-reconstruction-companion/references/offline-sync.md) · [Example and limits](evals/offline-sync/README.md)
|
||||
- **Workflow states and confirmation:** [Contract and boundaries](skills/rea-reconstruction-companion/references/blueprint.md#consequential-workflow-states) · [Example and limits](evals/workflow-edge-cases/README.md)
|
||||
- **Asynchronous results and cancellation:** [Contract and boundaries](skills/rea-reconstruction-companion/references/asynchronous-results.md) · [Example and limits](evals/asynchronous-results/README.md)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Evidence, compatibility and interface quality</strong></summary>
|
||||
|
||||
- **Responsive layouts and content extremes:** [Contract and boundaries](skills/rea-reconstruction-companion/references/visual-evidence.md#responsive-layouts-and-content-extremes) · [Example and limits](evals/responsive-layouts/README.md)
|
||||
- **Conflicting evidence:** [Contract and boundaries](skills/rea-reconstruction-companion/references/reconstruction-methods.md#conflicting-evidence-and-ambiguous-behavior) · [Example and limits](evals/conflicting-evidence/README.md)
|
||||
- **Security and privacy boundaries:** [Contract and boundaries](skills/rea-reconstruction-companion/references/reconstruction-security.md) · [Example and limits](evals/reconstruction-security/README.md)
|
||||
- **Numeric quantities, rounding and units:** [Contract and boundaries](skills/rea-reconstruction-companion/references/numeric-contracts.md) · [Example and limits](evals/numeric-contracts/README.md)
|
||||
- **Target version changes:** [Contract and boundaries](skills/rea-reconstruction-companion/references/version-changes.md) · [Example and limits](evals/version-change/README.md)
|
||||
- **Visual evidence:** [Contract and boundaries](skills/rea-reconstruction-companion/references/visual-evidence.md) · [Example and limits](evals/visual-evidence/README.md)
|
||||
- **Keyboard, focus and accessibility:** [Contract and boundaries](skills/rea-reconstruction-companion/references/interface-accessibility.md) · [Example and limits](evals/interface-accessibility/README.md)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Specialist request examples and boundaries</strong></summary>
|
||||
|
||||
For copy/cut/paste, use the focused [transfer guidance](skills/rea-reconstruction-companion/references/clipboard.md)
|
||||
and [offline example](evals/clipboard/README.md). Recover source/destination,
|
||||
representation, insertion/replacement, cut retention and undo/identity effects.
|
||||
|
||||
Investigation: “Explain only this editor's cut and paste behavior. Determine when
|
||||
the source is removed and what a rejected paste preserves. Keep unknowns explicit;
|
||||
do not implement or touch my clipboard or documents.”
|
||||
|
||||
Implementation: “Implement the specified copy/cut/paste slice in my reconstruction.
|
||||
Use synthetic data and a recording/failure clipboard double. Verify replacement,
|
||||
cut failure retention and undo/identity boundaries without real clipboard access.”
|
||||
|
||||
For settings, defaults and saved preferences, use the focused
|
||||
[preference guidance](skills/rea-reconstruction-companion/references/settings-preferences.md)
|
||||
and [offline worked example](evals/settings-preferences/README.md). Distinguish
|
||||
unset values from explicit false, zero or empty; recover scope/precedence,
|
||||
effective value/source and pending versus applied/saved changes.
|
||||
|
||||
Example: “Investigate only this app's display preferences. What do Apply, Cancel
|
||||
and Reset change, and what survives reopening this profile? Keep original
|
||||
observations separate from proposed improvements; stop after the contract.”
|
||||
For an authorized change: “Add this preference to my reconstruction; preserve
|
||||
unrelated settings, make its reset policy explicit, and test save/reopen and
|
||||
invalid or older data with disposable files.” The example's finite local policy
|
||||
does not establish original parity, crash recovery or concurrent-write safety.
|
||||
|
||||
For unsaved drafts and document departure, use the focused
|
||||
[save and navigation guidance](skills/rea-reconstruction-companion/references/unsaved-navigation.md)
|
||||
and [offline worked example](evals/unsaved-navigation/README.md). Distinguish
|
||||
draft/saved content and feedback; bind save completion to document/revision and
|
||||
check the actual save/discard/cancel destination.
|
||||
|
||||
Example: “Investigate only this editor's unsaved changes when switching notes.
|
||||
What survives save failure or another edit during save, and where does Cancel
|
||||
leave me? Keep unobserved behavior unresolved.” For an authorized implementation:
|
||||
“Add save/discard/cancel before leaving my reconstruction's edited note; retain
|
||||
the draft on failure and verify the selected destination with synthetic data.”
|
||||
The local example does not establish backend durability or browser-close safety.
|
||||
|
||||
For undo/redo, deletion or other destructive actions, use the focused
|
||||
[history and effect guidance](skills/rea-reconstruction-companion/references/undo-destructive.md)
|
||||
and [offline worked example](evals/undo-destructive/README.md). Recover values,
|
||||
identity/order, relevant view state, grouping and redo branching; distinguish
|
||||
confirmation/cancel and local restoration from irreversible or uncertain effects.
|
||||
|
||||
Example: “Investigate only this editor's delete and undo behavior. Check what
|
||||
returns, with which identity, position and selection; compare cancel, confirm and
|
||||
repeated confirm. Stop after the contract and unanswered questions.” For an
|
||||
authorized change: “Implement one undoable delete in my reconstruction, preserving
|
||||
item identity and order. Verify undo/redo and cancellation with synthetic data.”
|
||||
Neither prompt implies that local undo can reverse an external consequence.
|
||||
|
||||
For search, combined filters and sorted lists, use the focused
|
||||
[search and ordering guidance](skills/rea-reconstruction-companion/references/search-filter-sort.md)
|
||||
and [offline catalog example](evals/search-filter-sort/README.md). Recover matching,
|
||||
filter composition and comparator rules; check ordered identities, selection and
|
||||
page transitions. Keep observed original behavior separate from proposed changes.
|
||||
|
||||
Example: “Reconstruct this catalog's search, category/availability filters and
|
||||
price sorting. Establish case/accent/whitespace behavior, missing values and ties;
|
||||
check what happens to selection and pages when I filter, reorder, add or delete.”
|
||||
For a deliberate improvement, state the changed goal: “Preserve the observed
|
||||
matching rules, but retain selection by item identity when sorting.” The companion
|
||||
keeps the original observation and proposed acceptance separately testable.
|
||||
|
||||
For date/time-zone and localized data behavior, use the focused
|
||||
[date and locale contracts](skills/rea-reconstruction-companion/references/date-locale.md)
|
||||
and [bounded offline example](evals/date-locale/README.md). They distinguish
|
||||
calendar dates, instants and wall time, selected offset transitions, and display
|
||||
from parsing, sorting and stored/exported values.
|
||||
|
||||
For narrow windows, enlarged/long text or changing list volume, use the
|
||||
[responsive evidence guidance](skills/rea-reconstruction-companion/references/visual-evidence.md#responsive-layouts-and-content-extremes)
|
||||
and [small rendered example](evals/responsive-layouts/README.md). Keep visual
|
||||
agreement, reachable actions and accessibility evidence separately assessable;
|
||||
font simulation does not verify native browser zoom.
|
||||
|
||||
For state-dependent journeys, use [workflow state guidance](skills/rea-reconstruction-companion/references/blueprint.md#consequential-workflow-states)
|
||||
and the [bounded import-confirmation example](evals/workflow-edge-cases/README.md).
|
||||
Separate visible, pending and committed state; preserve untested transitions and
|
||||
distinguish local fault injection from original-target evidence.
|
||||
For delayed work, [trace asynchronous result applicability and uncertain effects](skills/rea-reconstruction-companion/references/asynchronous-results.md).
|
||||
The [controlled local example](evals/asynchronous-results/README.md) checks late
|
||||
values/errors/finalizers, cancellation distinctions and receipt reconciliation;
|
||||
its doubles do not establish real backend or concurrency guarantees.
|
||||
|
||||
For conflicting recordings, runtime results, docs or recollections, use
|
||||
[evidence reconciliation](skills/rea-reconstruction-companion/references/reconstruction-methods.md#conflicting-evidence-and-ambiguous-behavior)
|
||||
and the [bounded sorting example](evals/conflicting-evidence/README.md). Preserve
|
||||
source applicability, competing explanations and unresolved acceptance; newer or
|
||||
copy-derived evidence alone does not settle a claim.
|
||||
|
||||
For reconstructed slices that handle untrusted input, sensitive data or restricted
|
||||
operations, use the conditional [security boundary guidance](skills/rea-reconstruction-companion/references/reconstruction-security.md)
|
||||
and [small local worked example](evals/reconstruction-security/README.md). Keep
|
||||
original behavior, proposed controls and demonstrated protection distinct;
|
||||
simulated authentication does not establish real access protection.
|
||||
|
||||
</details>
|
||||
|
||||
For investigation, reconstruction, optimization and checkpoint/resume walkthroughs, see [Use](#use) and the [evaluation kit index](evals/README.md).
|
||||
|
||||
## Source and development
|
||||
|
||||
@@ -243,12 +483,19 @@ The full source is included: skill and research references, installer, optional
|
||||
|
||||
```sh
|
||||
node scripts/manifest.mjs
|
||||
node --test
|
||||
node scripts/run-checks.mjs
|
||||
python scripts/build_release.py
|
||||
```
|
||||
|
||||
Tests cover installation choices, backups, rollback, package integrity, and the local behavioral evaluation harness, including deliberately incorrect candidates. CI runs on Windows, macOS, and Linux with Node 20, 22, and 24. The included workflows use standard runners, skip private repositories, and add no model API calls or artifact/cache uploads. These checks do not establish reconstruction quality on every target or model.
|
||||
|
||||
The test runner first checks directory-link fixture setup in the same temporary
|
||||
parent/runtime. A denied or unknown probe leaves required validation incomplete;
|
||||
it does not explain unrelated assertion failures or prove administrator rights
|
||||
are needed. Original test failures and counts remain visible. See
|
||||
[test environment diagnostics](CONTRIBUTING.md#test-environment-diagnostics) for
|
||||
probe-only output, explicit full runs and supported local/CI next steps.
|
||||
|
||||
The [behavioral evaluation kit](evals/README.md) supplies portable tasks, raw evidence, independent executable oracles, and a human review rubric. It covers narrow functions, file formats, stateful workflows, original quirks versus approved improvements, ambiguous retries, inaccessible backends, contradictory evidence, hostile embedded instructions, cryptographic boundaries, and cost-sensitive tool choices. Paired selection tasks check that an implementation follows the requested compatibility or improvement goal; human review still verifies its reasoning and authorization. Running its harness tests is separate from evaluating an actual agent; model runs are not started automatically and require an environment established to incur no additional charge. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
<details>
|
||||
@@ -264,10 +511,26 @@ The [behavioral evaluation kit](evals/README.md) supplies portable tasks, raw ev
|
||||
|
||||
**Worked browser improvement (v1.3.14):** [preserve parity while improving save feedback and conflict recovery](evals/browser-improvement/README.md). Separate reference, parity and improved fixtures demonstrate failed-save feedback, retained drafts, explicit recovery and overlapping tab writes. The portable example includes acceptance requirements, replay routines and captured results, with cooperative-lock, controlled-injection and same-agent limits.
|
||||
|
||||
**Measured optimization (v1.3.24):** [test a plausible improvement against complete import workloads](evals/measured-optimization/README.md). A frozen protocol, literal behavior oracles, raw paired samples and executable evidence checks show a candidate that passes the selected correctness cases but fails to demonstrate an overall speed benefit. The original reconstruction remains unchanged; no dependency, model benchmark or CI timing threshold is added.
|
||||
|
||||
**Checkpoint/resume evaluation (v1.3.4):** [resume unchanged work or reassess a changed target](evals/resume/README.md). Two supplied-evidence cases exercise scoped reuse, partial implementation, preserved history and stale result handling. A local JSON-only checker performs 15 data comparisons per case; actual reasoning, actions and preservation still need human review. The skill includes a portable checkpoint guide, with no memory service, SDK, new dependency or automatic worker. The worked records are disclosed maintainer-authored examples, not cross-host/model certification.
|
||||
|
||||
**Measured environment example:** [revalidate behavior after a relevant setting changes](evals/resume/environment/README.md). Identical fixture bytes run under two controlled cap settings with a minimal local Node probe. Reports retain unaffected formatter evidence and recheck score/integration behavior. Cross-version drift and cross-host compatibility remain unverified.
|
||||
|
||||
**Keyboard commands (v1.3.49):** [recover input, command scope and guarded consequences](evals/keyboard-commands/README.md). The bounded offline profile freezes 72 literal context/routing/effect checkpoints and challenges 16 plausible faults after ordinary controls. Same-author model checks leave native input, IME, layouts, default actions and assistive behavior unrun.
|
||||
|
||||
**Navigation and history (v1.3.50):** [recover destination, entry restoration and departure ownership](evals/navigation-history/README.md). The bounded offline profile freezes 94 literal checkpoints across 33 scenarios and challenges 18 faults after ordinary controls. Same-author model checks establish no real browser history/reload, storage, native deep-link dispatch, original parity or model quality.
|
||||
|
||||
**Session and permissions (v1.3.51):** [recover identity/lifetime, retained work and access changes](evals/session-permissions/README.md). The bounded proposed policy checks 140 literal checkpoints across 23 sequences and challenges 16 faults after ordinary controls. Same-author checks establish no real authentication, authorization, revocation, durable storage, original parity or model quality.
|
||||
|
||||
**Combined navigation recovery (v1.3.52):** [compose departure, save and session ownership](evals/combined-navigation/README.md) in one proposed lifecycle. Twenty sequences check 189 literal state/effect checkpoints; 22 faults pass ordinary controls before a named mismatch. Guidance makes destination approval, captured revision, late receipts and conservative recovery stops explicit. This source-exposed synthetic trial establishes no real browser, service, storage, original parity or model quality.
|
||||
|
||||
**Browser workflow lab (v1.3.53):** [exercise native history and rendered recovery controls](evals/browser-workflow/README.md) using an existing browser and a disposable loopback fixture. The unchanged combined model gains a browser transport with inspectable URL/view reconciliation, exact-entry departure choices and obsolete-session entry handling. A native departure supersedes older save approval before source restoration, so a late acknowledgement cannot navigate from the already-moved entry. Twenty-six Node regressions use transport/HTTP doubles; actual browser acceptance requires the separate three-family procedure and DOM observations, including a visible-editor fault that ordinary controls miss. Authentication and service remain simulated; no persistence, reload/unload, security-enforcement, original-parity or model-quality claim follows.
|
||||
|
||||
**Numeric quantities (v1.3.38):** [separate input, canonical values, rounding and units](evals/numeric-contracts/README.md). One offline fixed length profile uses frozen literal expectations, actual storage/import/reopen and value/state rejection of faulty variants. Same-author finite checks establish a proposed contract, not original parity or a general decimal/locale engine.
|
||||
|
||||
**Search, filters and sorting (v1.3.39):** [exercise one proposed catalog profile](evals/search-filter-sort/README.md). Frozen literal orders and intermediate states cover matching, combined filters, missing/numeric values, stable ties, selection identity and page/data changes. Eleven faulty variants pass ordinary checks before selected assertions reject them; finite same-author checks establish neither original parity nor general locale/backend behavior.
|
||||
|
||||
</details>
|
||||
|
||||
## Support the project
|
||||
|
||||
+149
@@ -1,9 +1,111 @@
|
||||
# Behavioral evaluation kit
|
||||
|
||||
The [form-validation example](form-validation/README.md) freezes 69 literal
|
||||
intermediate checkpoints for validation timing, dependent-field policies and
|
||||
submission recovery. Eighteen faults pass ordinary controls before actual
|
||||
state/payload/effect assertions reject them. This proposed offline policy uses a
|
||||
submission double; it establishes neither original parity nor browser or backend
|
||||
behavior.
|
||||
|
||||
The [multi-selection and bulk-action example](multi-selection/README.md) freezes
|
||||
one proposed local profile with 70 literal intermediate states/attempts covering
|
||||
scope/identity, partial outcomes and retry/undo. Seventeen faults pass ordinary
|
||||
controls before a distinguishing assertion rejects them. Same-author synthetic
|
||||
checks do not establish original parity, real transactions or external undo.
|
||||
|
||||
The [offline clipboard example](clipboard/README.md) covers three proposed local
|
||||
families with synthetic documents and a recording/failure double: insertion or
|
||||
replacement, cut failure retention, and undo/identity boundaries. No real clipboard
|
||||
or user data is touched; finite scripted checks do not establish platform behavior.
|
||||
|
||||
The [settings/preferences example](settings-preferences/README.md) freezes a
|
||||
proposed finite profile with 36 literal intermediate states before candidate
|
||||
adaptation. Actual disposable local files and fresh instances exercise defaults,
|
||||
explicit overrides, Apply/Save/Cancel/reset and invalid/older data preservation.
|
||||
Twelve faults pass six ordinary checkpoints before named state/value/byte checks
|
||||
reject them. Same-author, source-exposed success does not establish original
|
||||
parity, crash recovery or concurrent-write safety.
|
||||
|
||||
The [unsaved changes/navigation example](unsaved-navigation/README.md) checks a
|
||||
proposed offline document profile with 72 literal intermediate snapshots and
|
||||
controlled promise completions. Twelve faulty variants pass ordinary controls
|
||||
before draft/saved/owner/intent state rejects them. One initial no-op oracle
|
||||
transcription correction is disclosed; source exposure and in-memory doubles
|
||||
leave original parity, durability and actual leave/close behavior unverified.
|
||||
|
||||
The [undo/destructive-action example](undo-destructive/README.md) freezes a
|
||||
proposed synthetic editor profile before implementation. Literal intermediate
|
||||
states cover history branching/grouping, identity/order/view restoration and
|
||||
confirm/cancel/repeated/permanent-effect boundaries. Ten disposable faults pass
|
||||
six declared ordinary checkpoints before named state/effect assertions reject
|
||||
them. Same-author checks and an in-memory effect double do not verify original
|
||||
parity, persistence, concurrency or reversal of real external effects.
|
||||
|
||||
The [search/filter/sort example](search-filter-sort/README.md) freezes one proposed
|
||||
offline catalog profile: literal ordered IDs and intermediate selection/page
|
||||
states, combined filters and meaningful matching/comparator boundaries. Eleven
|
||||
disposable faults pass ordinary checks before discriminating cases reject them.
|
||||
Same-author source-exposed checks do not establish original-app parity, general
|
||||
locale/search correctness or real backend/concurrent behavior.
|
||||
|
||||
The [date/locale example](date-locale/README.md) freezes a proposed synthetic
|
||||
Gregorian/Oslo profile before implementation. Literal expectations cover date-only
|
||||
meaning, midnight and offset transitions, explicit wall-time disambiguation and
|
||||
display versus canonical data. Four faulty candidates pass ordinary cases before
|
||||
selected boundaries reject their actual values. Same-author and finite-data limits
|
||||
remain explicit; no general calendar or recurrence system is provided.
|
||||
|
||||
The [responsive-layout example](responsive-layouts/README.md) records a small
|
||||
rendered viewport/content matrix, native-key interactions and three CSS defects
|
||||
that ordinary screenshots miss. Same-author evidence and report-regression tests
|
||||
retain unrun native zoom, device and assistive-technology checks explicitly.
|
||||
|
||||
The [workflow edge-case example](workflow-edge-cases/README.md) is a separate
|
||||
Pocket Ledger import-confirmation variant. Frozen literal sequences check
|
||||
pending/committed state, failed save then retry/cancel, stale confirmation and
|
||||
reopen. Ordinary workflows also pass on faulty variants rejected by transition
|
||||
checks. Same-author source-exposed controls do not prove original-app parity,
|
||||
crash/concurrency handling, live permissions or model understanding.
|
||||
|
||||
The [conflicting-evidence example](conflicting-evidence/README.md) contrasts a
|
||||
local runtime note, incomplete recording description, copy-derived reports and
|
||||
newer docs from another version. A controlled local check distinguishes two
|
||||
explanations while leaving capture causality, remote behavior and desired policy
|
||||
unresolved. Literal outcomes and misleading-decision rejection tests are finite
|
||||
same-author fixture checks, separate from live model understanding or app parity.
|
||||
|
||||
The [reconstruction security boundary](reconstruction-security/README.md) extends
|
||||
Pocket Ledger with bounded untrusted imports, synthetic sensitive-data projection
|
||||
checks and direct unauthorized-operation rejection. Simulated sessions and a text
|
||||
sink double are explicit; finite same-agent checks do not establish general
|
||||
security, independent acceptance or model performance.
|
||||
|
||||
The [interface accessibility slice](interface-accessibility/README.md) preserves
|
||||
the historical Pocket Board fixtures and demonstrates selected keyboard/focus,
|
||||
error correction and status cases. Live native-key evidence is separate from
|
||||
deterministic doubles and unrun screen-reader/device checks.
|
||||
|
||||
The [visual-evidence example](visual-evidence/README.md) uses frozen synthetic
|
||||
screens, a missing/cropped transition and conflicting narration. Fixed JSON
|
||||
cell checks challenge unsupported claims; manual interpretation is a disclosed
|
||||
same-agent review, not a general vision/language evaluation or actual video trial.
|
||||
|
||||
The [unavailable backend example](unavailable-backend/README.md) runs a proposed
|
||||
local notes boundary, finite synthetic replay and explicit deferred integration
|
||||
through this kit's existing checker. Its frozen literals and deliberate misleading
|
||||
completion/observation mutations are executable; manual goal/path decisions remain
|
||||
same-agent assessments, with no live provider or model evaluation.
|
||||
|
||||
The [version-change trial](version-change/README.md) combines frozen version evidence, dependency-selected revalidation and literal migration/recovery checks using the existing Pocket Ledger and resume mechanisms. It is a synthetic source-exposed same-agent example.
|
||||
|
||||
This kit evaluates outcomes of using the skill, separately from installation. It uses no model SDK, API key, package dependency, network service, or automatic agent invocation. Node.js 20+ is needed only for the executable oracle checks. The core skill is language- and provider-independent; JavaScript is a small local fixture interface, not a required reconstruction stack.
|
||||
|
||||
The [first-use review](first-use.md) walks through understanding, selected-feature reconstruction and checkpoint resumption with a varied natural-language scenario matrix. It records manual same-agent assessment separately from the existing executable contract tests; it does not run or score a model.
|
||||
|
||||
The [prioritization example and review](prioritization.md) show how a broad request becomes a short dependency-aware sequence with evidence-linked acceptance. Frozen manual constraints cover hard requirements, conflicting priorities, unavailable dependencies and corrections; existing executable checks remain separate.
|
||||
|
||||
The [quirk-decision assessment](quirk-decisions.md) applies preserve/improve/defer to existing selection, persistence and format fixtures, plus labeled hypothetical consumers. Its frozen reasoned scenarios and same-agent manual judgments are separate from executable profile and dependency regressions; no model is run or scored.
|
||||
|
||||
For a complete application to investigate with REA, use the [Pocket Tasks reference trial](reference/README.md). It provides an executable local target, a candidate evidence packet, a language-independent process interface for reconstructions, independent expected behavior, and a repeatable human review procedure. Its app and tools also use existing Node.js 20+. The supplied-evidence cases below and the reference trial are distinct; neither automatically invokes a model or establishes live-host compatibility.
|
||||
|
||||
The [worked reconstruction example](reference/reconstruction/README.md) shows the implementation/validation step with real captured results and disclosed prior source/reviewer exposure. It is reviewer/training material, not evidence for a fresh trial or an independent agent benchmark.
|
||||
@@ -12,6 +114,8 @@ The [worked browser improvement](browser-improvement/README.md) preserves a hist
|
||||
|
||||
The [file import/export trial](import-export/README.md) freezes a synthetic two-version format before reconstruction, tests both interoperability directions against literal expected values, and checks state preservation after rejection and controlled persistence failures. It includes a runnable example and checker mutations; source exposure and durability limits remain explicit.
|
||||
|
||||
The [measured optimization example](measured-optimization/README.md) freezes a bounded experiment on that reconstruction, retains raw paired timings and checks behavior plus measurement contracts. The candidate did not demonstrate an overall benefit; the historical implementation remains unchanged. Same-agent synthetic results apply only to the recorded workloads/runtime.
|
||||
|
||||
The [checkpoint/resume evaluation](resume/README.md) adds two supplied-evidence triage tasks and a JSON-only checker. It compares reuse and stale-result decisions without executing candidate code; explanation, preservation and actual actions still require human review.
|
||||
|
||||
## Run an evaluation
|
||||
@@ -41,11 +145,13 @@ checks. It is a disclosed source-exposed same-agent example, not a blind trial.
|
||||
| `quirk-improvement` | Correct that same behavior with explicit user approval | The same 18 inputs, with independent expectations for stable identity and clearing hidden selection |
|
||||
| `retry-uncertainty` | Investigate timeout/retry ambiguity without server access | Human review of competing explanations, misleading UI, proposed requirements and missing evidence |
|
||||
| `backend` | Specify an inaccessible service | Human review of supported/proposed/unknown claims |
|
||||
| `backend-local` | Exercise a proposed local substitute, finite synthetic replay and deferred integration | Local save/reopen, empty/error/pending state, replay mismatch and structured claims; no live-provider verification |
|
||||
| `conflict` | Reconcile contradictory evidence | Human review of provenance and distinguishing experiment |
|
||||
| `injection` | Analyze recovered hostile instructions | Human review of actual actions, not keyword matching |
|
||||
| `contract` | Assess a consumer/mock integration claim | Human review of real-provider verification limits |
|
||||
| `crypto` | Rebuild an authenticated message boundary | Published known answers, empty message/AAD boundaries, authentication failures and malformed fields in both directions |
|
||||
| `cost` | Choose among local, metered, and uncertain tools | Human review of actions and no-charge decisions |
|
||||
| `safe-changes` | Change an existing Pocket Ledger reconstruction while retaining custom listing and data contracts | Frozen baseline/separate variant, literal acceptance, injected save recovery and targeted faulty variants; same-agent, no model comprehension claim |
|
||||
| `resume` | Resume a partial checkpoint for an unchanged target | Separate JSON record checker; scoped reuse, unfinished work and pending parity |
|
||||
| `changed-target` | Resume after the selected target changes | Separate JSON record checker; stale findings/report applicability and preserved history |
|
||||
|
||||
@@ -91,3 +197,46 @@ Every report includes `execution`: the configured `timeoutMs`, wall-clock `elaps
|
||||
For `harness-error`, inspect `reason` first. `timeout` means the worker exceeded its configured budget; `output-limit` means captured output exceeded the buffer limit. `candidate-process-failed` calls for checking the exit code, signal and process error code, then reviewing the local candidate's imports/export and process behavior. `invalid-worker-output`, `incomplete-worker-output` and `invalid-worker-result` call for checking unintended stdout writes, early exits and envelope shape. Review sensitive local output only in an appropriate environment. Preserve the failing report and environment before investigating; an unchanged retry passing does not identify the original cause. Do not count a harness failure as a caught value mismatch or relax the limit to make a test pass.
|
||||
|
||||
Count a deliberate defect as caught by an assertion only when its targeted fixture has reason `value-mismatch`. An exception, non-JSON result, timeout, or worker failure is separate evidence, not proof that the intended assertion was effective. Even a successful comparison report leaves `humanReview: pending`; it cannot establish authority, scope, side effects, no-charge decisions, or release approval. The worker protocol is a diagnostics format, not tamper resistance or a sandbox.
|
||||
|
||||
## Controlled asynchronous results
|
||||
|
||||
For offline editing, reconnect and conflicting revisions, the
|
||||
[single-slot synchronization example](offline-sync/README.md) freezes complete
|
||||
intermediate states and separates attempts, synthetic effects and acknowledgements.
|
||||
It exercises safe known-no-effect retry, unknown-outcome reconciliation and explicit
|
||||
rebase/discard, without persistent storage, real service access or exactly-once claims.
|
||||
|
||||
[One bounded local example](asynchronous-results/README.md) executes actual
|
||||
promise callbacks with frozen intermediate expectations and selected faulty
|
||||
variants. It distinguishes stale values/errors/finalizers, abort requests and
|
||||
worker acknowledgements, and uncertain effects with scoped local receipts.
|
||||
Deterministic doubles do not establish real network, backend cancellation,
|
||||
concurrent delivery or original-app parity.
|
||||
|
||||
## Drag, drop and reordering
|
||||
|
||||
[One bounded board example](drag-drop/README.md) compares literal stable
|
||||
identities/order, preview/applicability, cancel/failure, move/copy and undo
|
||||
conflicts against independently recorded local effects. Fifteen faulty variants
|
||||
pass ordinary controls before named assertions reject them. The explicit move
|
||||
operation shares the gesture contract; no actual browser/OS input or accessibility
|
||||
behavior, service concurrency or original parity is established.
|
||||
|
||||
## Numeric quantity contracts
|
||||
|
||||
[One bounded length example](numeric-contracts/README.md) separates fixed input
|
||||
grammars, exact canonical values, display rounding and unit/storage/export
|
||||
contracts. Frozen literals and actual save/import/reopen reject selected faulty
|
||||
variants without decimal packages, host-locale changes or original-parity claims.
|
||||
|
||||
For keyboard shortcuts and command scope, the [bounded command example](keyboard-commands/README.md) freezes 72 literal intermediate contexts, routing/consumption decisions and independent effects in 36 proposed-policy scenarios. Sixteen faulty variants pass five ordinary checkpoints before a named mismatch. No native dispatch, IME, actual layout, focus traversal or assistive behavior is established.
|
||||
|
||||
For direct links and history restoration, the [bounded navigation example](navigation-history/README.md) freezes 94 literal intermediate states and separate adapter effects in 33 proposed-policy scenarios. Eighteen faulty variants pass five ordinary checkpoints before a named mismatch. Real browser history, reload, storage, native deep-link dispatch and original parity remain unverified.
|
||||
|
||||
For reload/reopen and persistent draft recovery, the separate
|
||||
[browser draft lab](draft-recovery/README.md) distinguishes native saved records,
|
||||
checkpointed drafts and memory. Its finite browser observations and controlled
|
||||
faults remain separate from source doubles, simulated identity and release review.
|
||||
Its explicit conflict controls compare retained/latest values without replacing
|
||||
them, retain a scoped separate copy and revalidate pending choices at the locked
|
||||
effect. Copy retention/capacity and acknowledgement limits are proposed policies.
|
||||
@@ -37,6 +37,10 @@ and stored expected fields. Tests exercise the actual CLI comparison path with
|
||||
six wrong candidates: wrong key, inert flat-99 fee, lost checksum wrap, wrong
|
||||
error stream, wrong exit and acceptance of trailing-newline fee input. Tests also
|
||||
reject missing/duplicate/drifted observations and modified artifact/oracle identity.
|
||||
The original archived capture is also hash-pinned; its declared subprocess method,
|
||||
Windows environment and CRLF-only normalization are checked. A changed capture
|
||||
must remain separate from that historical record. These checks cannot authenticate
|
||||
arbitrary new observations or prove that their author actually ran a process.
|
||||
Thirty cases do not exhaust the input space or establish global equivalence.
|
||||
|
||||
## Repeat the investigation
|
||||
|
||||
@@ -4,10 +4,20 @@ import { fileURLToPath } from 'node:url';
|
||||
import { root, verifyFreeze, read, locked, hash } from './freeze.mjs';
|
||||
import { capture, comparable } from './process.mjs';
|
||||
export function checkSaved(cases, saved) {
|
||||
// This entrypoint accepts the existing Windows subprocess capture format only.
|
||||
// Metadata checks preserve its declared authority; they cannot prove capture truth.
|
||||
if (saved?.schema !== 1 || saved.method !== 'Ordinary local Node spawnSync of owned compiled reference; not REA process capture'
|
||||
|| saved.normalization !== 'CRLF to LF only; raw streams retained' || saved.environment?.platform !== 'win32'
|
||||
|| !['x64', 'ia32', 'arm64'].includes(saved.environment.arch) || typeof saved.environment.node !== 'string'
|
||||
|| !/^v\d+\.\d+\.\d+$/.test(saved.environment.node)) throw new Error('Unsupported observation provenance');
|
||||
if (saved.artifactSha256 !== locked['reference/Parcel.exe'] || saved.corpusSha256 !== locked['corpus.json'] || !Array.isArray(saved.rows) || saved.rows.length !== cases.length) throw new Error('Observation identity/coverage mismatch');
|
||||
for (const [index, c] of cases.entries()) {
|
||||
const row = saved.rows[index];
|
||||
if (row.id !== c.id || row.behavior !== c.behavior || !isDeepStrictEqual(row.args, c.args) || !isDeepStrictEqual(comparable(row.raw), { out: c.out, err: c.err, exit: c.exit })) throw new Error('Observation mismatch: ' + c.id);
|
||||
// This capture format contains completed streams and exit only. Reject
|
||||
// contradictory failure/partial fields instead of projecting them away.
|
||||
if (!row?.raw || !isDeepStrictEqual(Object.keys(row.raw).sort(), ['err', 'exit', 'out'])
|
||||
|| row.id !== c.id || row.behavior !== c.behavior || !isDeepStrictEqual(row.args, c.args)
|
||||
|| !isDeepStrictEqual(comparable(row.raw), { out: c.out, err: c.err, exit: c.exit })) throw new Error('Observation mismatch: ' + c.id);
|
||||
}
|
||||
}
|
||||
export function check({ candidate = path.join(root, 'candidate.mjs'), freshReference = process.platform === 'win32' } = {}) {
|
||||
|
||||
@@ -6,6 +6,8 @@ export const root = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const hash = data => createHash('sha256').update(data).digest('hex');
|
||||
export const read = name => fs.readFileSync(path.join(root, name));
|
||||
export const locked = Object.freeze({
|
||||
// Historical pre-candidate capture, not a requirement that fresh hosts match it.
|
||||
'observed-reference.json': '93d7e7b8a036b6b7626b6e676618f446dae0f7b08e0107ebea2f0e7d7aa070fc',
|
||||
'reference/Parcel.exe': '6f4e7e1d49e03f60adf9deb4929aafbe3fdd6babb628881807c63efb3ee089b4',
|
||||
'corpus.json': 'cec3c120b8e39a8025af07d6f52ee705b075d7887c6b4d4f507b5d0a28542c26',
|
||||
'build-source/Parcel.cs': '328018920a729784b96753283fddb253a6b1978256a49ee57fc8ec9f8a34cf77',
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Proposed completion policy in existing records
|
||||
|
||||
No original application or service was supplied. These are proposed requirements
|
||||
and same-author local observations after execution, not recovered internals.
|
||||
|
||||
| Existing record extension | Requirement and policy | Selected acceptance |
|
||||
| --- | --- | --- |
|
||||
| F-preview / R-preview | A read belongs to its operation, triggering input and owner instance. Latest applicable request owns the view. | Newer success/error survives old success; same input after owner replacement cannot reaccept the old value. |
|
||||
| F-cancel / R-guard | Guard success, failure and finalization separately. Retain historical operation outcomes. | Old failure cannot replace current status/error; old finalizer cannot stop current loading. |
|
||||
| F-cancel / R-guard | Dismissal invalidates the view but leaves work pending. Abort signals a request; only explicit double acknowledgement establishes worker cancellation. | Dismissed completion records outcome without updating view. Abort-requested state differs from confirmed cancelled; successful work can still finish after abort but is ignored. |
|
||||
| F-save / R-retry | A lost response leaves an effect unknown. One writer retains exact key/payload; pending/unknown/completed work is not blindly retried. | Commit-before-response-loss changes actual store once; retry is blocked, applicable lookup recovers receipt. Reconciliation unavailable leaves unknown; settled no-effect lookup permits bounded explicit retry. |
|
||||
| U-concurrency / U-backend | Double is one process/instance, one pending effect attempt, receipts retained only until disposal; cancellation is injected read-worker acknowledgement. | Local callback paths only. Pending lookup is unknown, retained key rejects different payload, repeated key returns same receipt; fresh key or fresh instance has no cross-key/instance guarantee. Real network/concurrent/crash semantics remain unverified. |
|
||||
|
||||
The superseded read's state can be `completed`/`failed` while its value is
|
||||
inapplicable. UI loading can stop on abort request while operation remains
|
||||
pending. A write's transport failure is `unknown`, distinct from the double's
|
||||
explicit `not-applied` rejection. These dimensions cannot be inferred from one
|
||||
screen label. Receipt lookup must bind the exact intent; unknown lookup is not
|
||||
absence. The candidate deliberately exposes no replacement write over an
|
||||
unresolved intent. A real requirement for durable pending operations needs
|
||||
separate storage and recovery evidence.
|
||||
|
||||
Changing owner identity, guard policy, worker acknowledgement, effect failure
|
||||
location, provider/key retention or reconciliation consistency invalidates
|
||||
dependent acceptance until reassessed. Existing codec/workflow reference bytes
|
||||
remain historical under their own scope. See the standalone
|
||||
[method](../../skills/rea-reconstruction-companion/references/asynchronous-results.md).
|
||||
@@ -0,0 +1,47 @@
|
||||
# Controlled asynchronous completions
|
||||
|
||||
From the repository root with existing Node.js 20+ (no dependencies):
|
||||
|
||||
```sh
|
||||
node evals/asynchronous-results/check.mjs
|
||||
node --test tests/asynchronous-results.test.mjs
|
||||
```
|
||||
|
||||
The [contract](CONTRACT.md) extends the existing finding/requirement/acceptance
|
||||
records. [Expectations](expectations.json) are separately authored literal
|
||||
intermediate states, [frozen](identity.json) before candidate implementation and
|
||||
first execution. The [candidate](candidate.mjs) handles actual Promise success,
|
||||
rejection and finalization callbacks. The [controlled doubles](controlled.mjs)
|
||||
release individual completions explicitly; there are no sleeps or network calls.
|
||||
Each scenario starts with fresh state and a new in-memory effect store. No real
|
||||
user data, installed copy, account, provider, scheduler or host setting is changed.
|
||||
|
||||
The reader's `view` tuple is `[owner, visible, input, value, error, status,
|
||||
loading]`. Operation strings capture `id:owner:input:state:abortRequested` for
|
||||
these synthetic delimiter-free values. They retain the old outcome without
|
||||
applying it to the current view. Write intent tuples are `[key, payload, state,
|
||||
receipt]`; effect values and send-attempt counts are compared independently.
|
||||
|
||||
Ordinary read success/error and write success/rejection/retry precede selected
|
||||
old/new result orders, stale rejection/finalizer, same-input owner replacement,
|
||||
dismissal, abort with ignored success, explicit worker cancellation, lost response
|
||||
after commit, lost response before apply and unavailable reconciliation. Seven
|
||||
deliberate faulty candidates pass every ordinary comparison before a specific
|
||||
boundary value/state assertion rejects them. Load/runtime failures propagate;
|
||||
they are not useful mutation kills. This is finite chosen ordering coverage,
|
||||
not exhaustive event interleavings, code coverage or a general mutation score.
|
||||
|
||||
Cancellation acknowledgement is explicitly injected by a read-worker double;
|
||||
it is not inferred from `signal.aborted`. Dismissal leaves the operation running.
|
||||
The effect double retains key/payload receipts for one instance's lifetime,
|
||||
serializes one in-flight attempt and returns `not-applied` only after it has
|
||||
settled without an effect. That deliberately stronger local lookup contract does
|
||||
not describe a real server's eventually consistent status lookup. Fresh keys
|
||||
can duplicate effects; another instance has no receipts. No crash persistence,
|
||||
eviction/expiry, concurrent writers, distributed exactly-once effects, real
|
||||
network/fetch/server cancellation, browser UI or original-app parity is tested.
|
||||
|
||||
All source, literals and tests are exposed and same-author. Frozen hashes detect
|
||||
drift, not truth or independent authorship. These checks exercise a proposed
|
||||
local policy, not model instruction following. Keep historical workflow/codec
|
||||
oracles unchanged; their synchronous results do not become asynchronous evidence.
|
||||
@@ -0,0 +1,64 @@
|
||||
// Proposed local policy; transport and provider are supplied controlled doubles.
|
||||
export function createReader(transport) {
|
||||
let owner = 'owner-1', active = null;
|
||||
let view = [owner, true, '', null, null, 'idle', false];
|
||||
const operations = new Map();
|
||||
const current = op => active === op && owner === op.owner && view[1];
|
||||
const accepts = op => current(op) && !op.controller.signal.aborted;
|
||||
return {
|
||||
start(id, input) {
|
||||
if (operations.has(id)) throw Error('duplicate operation');
|
||||
const op = {id, input, owner, state:'pending', controller:new AbortController()};
|
||||
operations.set(id, op); active = op;
|
||||
view = [owner, true, input, null, null, 'pending', true];
|
||||
// No sleep, global timer, network request or auto-abort on replacement.
|
||||
op.done = transport.request(id, input, op.controller.signal).then(value => {
|
||||
op.state = 'completed';
|
||||
if (accepts(op)) { view[3] = value; view[5] = 'completed'; }
|
||||
}, error => {
|
||||
op.state = error.kind === 'cancelled' ? 'cancelled' : 'failed';
|
||||
if (op.state === 'cancelled' && current(op)) view[5] = 'cancelled';
|
||||
else if (accepts(op)) { view[4] = error.message; view[5] = 'failed'; }
|
||||
}).finally(() => {
|
||||
if (accepts(op)) view[6] = false;
|
||||
});
|
||||
},
|
||||
dismiss() { active = null; view[1] = false; view[5] = 'dismissed'; view[6] = false; },
|
||||
owner(value) { owner = value; active = null; view = [owner, true, '', null, null, 'idle', false]; },
|
||||
abort() {
|
||||
if (!active || active.state !== 'pending') throw Error('no pending operation');
|
||||
active.controller.abort(); view[5] = 'abort-requested'; view[6] = false;
|
||||
},
|
||||
done(id) { return operations.get(id).done; },
|
||||
snapshot() {
|
||||
return {view:[...view], operations:[...operations.values()].map(op =>
|
||||
`${op.id}:${op.owner}:${op.input}:${op.state}:${op.controller.signal.aborted}`)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createWriter(provider) {
|
||||
let key = null, value = null, state = 'idle', receipt = null, done;
|
||||
function send() {
|
||||
state = 'pending';
|
||||
done = provider.send(key, value).then(result => {
|
||||
state = result.state; receipt = result.receipt ?? null;
|
||||
}, () => { state = 'unknown'; });
|
||||
}
|
||||
return {
|
||||
start(id, payload) {
|
||||
// One logical effect per writer, no replacement of an unresolved intent.
|
||||
if (key !== null) throw Error('intent already exists');
|
||||
key = id; value = payload; send();
|
||||
},
|
||||
retry() { if (state === 'not-applied') send(); },
|
||||
async reconcile() {
|
||||
if (state !== 'unknown') return;
|
||||
// Double lookup is serialized after settlement; a real empty lookup can race.
|
||||
const result = await provider.lookup(key, value);
|
||||
state = result.state; receipt = result.receipt ?? null;
|
||||
},
|
||||
done() { return done; },
|
||||
snapshot() { return [key, value, state, receipt]; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import fs from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
import {pathToFileURL} from 'node:url';
|
||||
import {createReader, createWriter} from './candidate.mjs';
|
||||
import {controlledReads, controlledEffects} from './controlled.mjs';
|
||||
|
||||
export function frozen() {
|
||||
const bytes = fs.readFileSync(new URL('./expectations.json', import.meta.url));
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('./identity.json', import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256)
|
||||
throw Error('changed frozen expectations');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export async function compare({reader = createReader, writer = createWriter, group = 'all'} = {}) {
|
||||
if (!['all','ordinary','boundary'].includes(group)) throw Error('unknown group');
|
||||
const oracle = frozen(), rows = [];
|
||||
function record(id, index, actual, expected) {
|
||||
rows.push({id, step:index, passed:isDeepStrictEqual(actual,expected), actual, expected});
|
||||
}
|
||||
for (const scenario of oracle.reads.filter(s => group === 'all' || s.group === group)) {
|
||||
const transport = controlledReads(), app = reader(transport);
|
||||
for (const [i, [action, view, operations]] of scenario.steps.entries()) {
|
||||
const [command, ...args] = action;
|
||||
if (['resolve','reject','cancelled'].includes(command)) {
|
||||
transport.settle(command,...args); await app.done(args[0]);
|
||||
} else app[command](...args);
|
||||
record(scenario.id,i,app.snapshot(),{view,operations});
|
||||
}
|
||||
}
|
||||
for (const scenario of oracle.writes.filter(s => group === 'all' || s.group === group)) {
|
||||
const provider = controlledEffects(), app = writer(provider);
|
||||
for (const [i, [action, intent, effects, attempts]] of scenario.steps.entries()) {
|
||||
const [command, ...args] = action;
|
||||
if (command === 'deliver') { provider.deliver(...args); await app.done(); }
|
||||
else if (command === 'reconcile-unavailable') { provider.unavailable(); await app.reconcile(); }
|
||||
else await app[command](...args);
|
||||
record(scenario.id,i,{intent:app.snapshot(),...provider.snapshot()},{intent,effects,attempts});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
const rows = await compare();
|
||||
console.log(JSON.stringify({kind:'controlled-local-example',runtime:process.version,
|
||||
comparisons:rows.length,passed:rows.filter(r=>r.passed).length,rows,
|
||||
originalParity:'unverified',realNetworkConcurrency:'unverified',nonAuthorReview:'pending'},null,2));
|
||||
if (rows.some(r=>!r.passed)) process.exitCode = 1;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Test infrastructure, not a real backend or scheduler implementation.
|
||||
function deferred() {
|
||||
let resolve, reject;
|
||||
const promise = new Promise((a, b) => {resolve = a; reject = b;});
|
||||
return {promise, resolve, reject};
|
||||
}
|
||||
export function controlledReads() {
|
||||
const requests = new Map();
|
||||
return {
|
||||
request(id, input, signal) {
|
||||
if (requests.has(id)) throw Error('duplicate transport id');
|
||||
const item = {...deferred(), input, signal, settled:false};
|
||||
requests.set(id, item); return item.promise;
|
||||
},
|
||||
settle(kind, id, value) {
|
||||
const item = requests.get(id);
|
||||
if (!item || item.settled) throw Error('unknown or settled transport id');
|
||||
item.settled = true;
|
||||
if (kind === 'resolve') item.resolve(value);
|
||||
else if (kind === 'cancelled') {
|
||||
if (!item.signal.aborted) throw Error('no abort requested');
|
||||
item.reject({kind:'cancelled', message:'worker confirmed cancellation'});
|
||||
} else if (kind === 'reject') item.reject({kind:'failure', message:value});
|
||||
else throw Error('unknown completion');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// One process, one instance, serialized delivery. Entries retained until disposal.
|
||||
// No eviction, crash recovery, cross-instance or concurrent duplicate guarantee.
|
||||
export function controlledEffects() {
|
||||
const records = new Map(), effects = [];
|
||||
let pending = null, attempts = 0, unavailable = false;
|
||||
function commit(key, value) {
|
||||
const prior = records.get(key);
|
||||
if (prior && prior.value !== value) throw Error('key payload mismatch');
|
||||
if (prior) return prior.receipt;
|
||||
const receipt = `receipt-${effects.length + 1}`;
|
||||
effects.push(value); records.set(key, {value, receipt}); return receipt;
|
||||
}
|
||||
return {
|
||||
send(key, value) {
|
||||
if (pending) throw Error('double supports one in-flight attempt');
|
||||
pending = {...deferred(), key, value}; attempts++; return pending.promise;
|
||||
},
|
||||
deliver(mode) {
|
||||
if (!pending) throw Error('no in-flight attempt');
|
||||
const item = pending; pending = null;
|
||||
if (mode === 'ok' || mode === 'commit-lost') {
|
||||
let receipt;
|
||||
try { receipt = commit(item.key,item.value); }
|
||||
catch (error) { item.reject(error); return; }
|
||||
if (mode === 'ok') item.resolve({state:'completed',receipt});
|
||||
else item.reject(Error('response lost'));
|
||||
} else if (mode === 'not-applied') item.resolve({state:'not-applied'});
|
||||
else if (mode === 'lost') item.reject(Error('disconnected before apply'));
|
||||
else throw Error('unknown delivery mode');
|
||||
},
|
||||
async lookup(key, value) {
|
||||
if (unavailable || pending) return {state:'unknown'};
|
||||
const prior = records.get(key);
|
||||
if (prior && prior.value !== value) throw Error('key payload mismatch');
|
||||
return prior ? {state:'completed',receipt:prior.receipt} : {state:'not-applied'};
|
||||
},
|
||||
unavailable() { unavailable = true; },
|
||||
snapshot() { return {effects:[...effects], attempts}; }
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"expectationsSha256": "c4fdb410281eb9900a41303e83d0ae665d610190da8c5122f937099099b827d1",
|
||||
"basis": "Same-author proposed contract, authored literals frozen before candidate implementation and first execution; no original target"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Frozen reviewer literals; proposed synthetic behavior, no candidate imports.
|
||||
const observation = (mode, overrides = {}) => ({ mode, draft: '', saved: null,
|
||||
items: null, status: 'idle', error: null, synchronization: 'not-requested',
|
||||
remoteVerified: false, adapterCalls: 0, storeBytes: null, ...overrides });
|
||||
const saved = { draft: 'B', saved: 'B', synchronization: 'pending', storeBytes: '{"text":"B"}' };
|
||||
const request = { operation: 'list', collection: 'notes' };
|
||||
const list = { type: 'list', collection: 'notes' };
|
||||
const edit = { type: 'edit', text: 'B' };
|
||||
const save = { type: 'save' };
|
||||
const reopen = { type: 'reopen' };
|
||||
const record = response => ({ request, response });
|
||||
|
||||
export function backendCases() {
|
||||
return [
|
||||
{ label: 'local-save-reopen', input: { mode: 'local', actions: [edit, save, reopen] },
|
||||
expected: observation('local', { ...saved, status: 'reopened' }) },
|
||||
{ label: 'local-empty-success', input: { mode: 'local', actions: [list] },
|
||||
expected: observation('local', { items: [], status: 'ready', adapterCalls: 1 }) },
|
||||
{ label: 'local-save-list', input: { mode: 'local', actions: [edit, save, list] },
|
||||
expected: observation('local', { ...saved, items: ['B'], status: 'ready', adapterCalls: 1 }) },
|
||||
{ label: 'empty-note-is-data', input: { mode: 'local', actions: [save, reopen, list] },
|
||||
expected: observation('local', { saved: '', items: [''], status: 'ready', synchronization: 'pending', storeBytes: '{"text":""}', adapterCalls: 1 }) },
|
||||
{ label: 'exact-text', input: { mode: 'local', actions: [{ type: 'edit', text: ' ø\n ' }, save, reopen] },
|
||||
expected: observation('local', { draft: ' ø\n ', saved: ' ø\n ', synchronization: 'pending', status: 'reopened', storeBytes: '{"text":" ø\\n "}' }) },
|
||||
{ label: 'failed-save-keeps-draft-bytes', input: { mode: 'local', initialBytes: '{ "text": "A" }', actions: [edit, { type: 'save', reject: true }] },
|
||||
expected: observation('local', { draft: 'B', saved: 'A', status: 'error', error: 'local-write-failed', synchronization: 'pending', storeBytes: '{ "text": "A" }' }) },
|
||||
{ label: 'unsaved-reopen', input: { mode: 'local', initialBytes: '{"text":"A"}', actions: [edit, reopen] },
|
||||
expected: observation('local', { draft: 'A', saved: 'A', status: 'reopened', synchronization: 'pending', storeBytes: '{"text":"A"}' }) },
|
||||
{ label: 'malformed-store-preserved', input: { mode: 'local', initialBytes: '{broken', actions: [edit, save] },
|
||||
expected: observation('local', { status: 'error', error: 'malformed-store', storeBytes: '{broken' }) },
|
||||
{ label: 'deferred-is-unavailable', input: { mode: 'deferred', actions: [list] },
|
||||
expected: observation('deferred', { status: 'error', error: 'unavailable', adapterCalls: 1 }) },
|
||||
{ label: 'deferred-local-still-pending', input: { mode: 'deferred', actions: [edit, save, reopen, list] },
|
||||
expected: observation('deferred', { ...saved, status: 'error', error: 'unavailable', adapterCalls: 1 }) },
|
||||
{ label: 'replay-empty-success', input: { mode: 'replay', recordings: [record({ kind: 'ok', items: [] })], actions: [list] },
|
||||
expected: observation('replay', { items: [], status: 'ready', adapterCalls: 1 }) },
|
||||
{ label: 'replay-does-not-synchronize', input: { mode: 'replay', recordings: [record({ kind: 'ok', items: ['recorded'] })], actions: [edit, save, list] },
|
||||
expected: observation('replay', { ...saved, items: ['recorded'], status: 'ready', adapterCalls: 1 }) },
|
||||
{ label: 'timeout-no-retry', input: { mode: 'replay', recordings: [record({ kind: 'timeout' }), record({ kind: 'ok', items: ['unused'] })], actions: [list] },
|
||||
expected: observation('replay', { status: 'error', error: 'timeout', adapterCalls: 1 }) },
|
||||
{ label: 'unavailable-not-empty', input: { mode: 'replay', recordings: [record({ kind: 'unavailable' })], actions: [list] },
|
||||
expected: observation('replay', { status: 'error', error: 'unavailable', adapterCalls: 1 }) },
|
||||
{ label: 'malformed-response', input: { mode: 'replay', recordings: [record({ kind: 'ok', items: null })], actions: [list] },
|
||||
expected: observation('replay', { status: 'error', error: 'malformed-response', adapterCalls: 1 }) },
|
||||
{ label: 'extra-response-field', input: { mode: 'replay', recordings: [record({ kind: 'ok', items: [], synchronized: true })], actions: [list] },
|
||||
expected: observation('replay', { status: 'error', error: 'malformed-response', adapterCalls: 1 }) },
|
||||
{ label: 'request-mismatch', input: { mode: 'replay', recordings: [record({ kind: 'ok', items: [] })], actions: [{ type: 'list', collection: 'other' }] },
|
||||
expected: observation('replay', { status: 'error', error: 'replay-mismatch', adapterCalls: 1 }) },
|
||||
{ label: 'replay-exhausted-keeps-stale-items', input: { mode: 'replay', recordings: [record({ kind: 'ok', items: ['recorded'] })], actions: [list, list] },
|
||||
expected: observation('replay', { items: ['recorded'], status: 'error', error: 'replay-mismatch', adapterCalls: 2 }) },
|
||||
{ label: 'local-unknown-collection', input: { mode: 'local', actions: [{ type: 'list', collection: 'other' }] },
|
||||
expected: observation('local', { status: 'error', error: 'unavailable', adapterCalls: 1 }) },
|
||||
...[
|
||||
['invalid-mode', { mode: 'remote', actions: [] }],
|
||||
['too-many-actions', { mode: 'local', actions: Array(17).fill(list) }],
|
||||
['oversized-text', { mode: 'local', actions: [{ type: 'edit', text: 'x'.repeat(257) }] }],
|
||||
['unknown-action', { mode: 'local', actions: [{ type: 'upload' }] }],
|
||||
].map(([label, input]) => ({ label, input, expected: null })),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
# Browser-backed combined workflow lab
|
||||
|
||||
This repository-only, dependency-free fixture exercises a **proposed** browser
|
||||
transport around the unchanged [combined model](../combined-navigation/README.md).
|
||||
It is portable across models and automation providers. Authentication, accounts,
|
||||
grants, read/write responses and service commits remain visibly **simulated**.
|
||||
Standalone skill installations omit evals and tests.
|
||||
|
||||
Run `node evals/browser-workflow/server.mjs` using the existing Node >=20 runtime.
|
||||
Open the printed `http://127.0.0.1:PORT/items/a` in a fresh disposable tab/profile
|
||||
that contains no private user data. The server binds only loopback, accepts only
|
||||
GET with its loopback Host, serves four named modules and the fixture page, and
|
||||
sets no cookies or persistent storage. Close task-owned tabs and stop only this
|
||||
server with Ctrl+C after recording evidence. No install, external service, model
|
||||
call, network API, telemetry or authentication credential is needed.
|
||||
|
||||
Use one new tab for each independent procedure below. Complete reads explicitly;
|
||||
the operator's Commit and Complete controls separate a synthetic service effect
|
||||
from its acknowledgement. Fail represents a read error or unknown write response,
|
||||
not proof that a write was cancelled. A write can still commit after Fail.
|
||||
|
||||
## Browser contract and evidence
|
||||
|
||||
The native History API owns actual entries. `popstate` arrives **after** the URL
|
||||
has moved. For a dirty or unresolved departure the fixture records that movement,
|
||||
hides/disables the editor and pauses at **Resume URL reconciliation**. This
|
||||
operator gate makes the intermediate DOM inspectable without timing sleeps. It
|
||||
controls when `history.go` is issued; it does not synthesize a popstate event.
|
||||
The exact native departure is registered with a fresh proposal ID at popstate,
|
||||
before restoration. Its dialog stays hidden until Resume restores the source
|
||||
entry. This supersedes any older save-and-leave approval immediately: an old save
|
||||
acknowledgement can certify only its captured revision, without navigating from
|
||||
the already-moved native entry. Resume does not register the proposal a second
|
||||
time. Even if that acknowledgement leaves a clean draft, the new native proposal
|
||||
still requires its own exact choice after restoration.
|
||||
An approved traversal pauses again before issuing the native move to its exact
|
||||
destination. Clean traversals accept the already-moved native entry directly.
|
||||
This is a bounded lab procedure, not a production navigation framework or a
|
||||
pre-commit traversal veto.
|
||||
|
||||
Push truncates forward entries; replace retains the current key. Same URL entries
|
||||
have separate keys. A session change replaces the current physical entry with a
|
||||
new epoch while the model resets its volatile history. Older native entries still
|
||||
exist: traversing one restores the current epoch's entry without loading the old
|
||||
owner or reviving a choice. Foreign entries and unexpected traversal during
|
||||
reconciliation stop with the editor hidden. Leaving the fixture document entirely
|
||||
is outside this same-document procedure; the fixture cannot prevent native close,
|
||||
external navigation or an out-of-range Back from unloading it. Start in a fresh
|
||||
tab and do not traverse before the fixture's first entry.
|
||||
|
||||
Before adapting the transport, the author froze the separately reasoned proposed
|
||||
outcomes in [expected-before-adaptation.json](expected-before-adaptation.json).
|
||||
These are human-readable expectations, not a machine oracle. Record each action,
|
||||
native address/entry, rendered editor/dialog value and visibility, revision,
|
||||
loading/saving, proposal, controlled calls and independently recorded service
|
||||
effects at intermediate points. The expandable rendered observation record
|
||||
contains these fixture diagnostics; supplement it with actual DOM/role/value,
|
||||
address and screenshot observations. A record's `hidden` event annotation alone
|
||||
does not prove the DOM was hidden. Synthetic operator diagnostics deliberately
|
||||
show old operation payloads and are separate from the current owner's editor.
|
||||
They implement no protected service or confidentiality boundary.
|
||||
|
||||
## Three bounded families
|
||||
|
||||
1. **Native history and dirty editor.** Complete read1 at A, Push B / complete2,
|
||||
Push C / complete3, edit C1. Use the browser's actual Back. Observe native B,
|
||||
committed C, editor hidden, no dialog or write. Resume: native C, exact
|
||||
traversal proposal for B/key2, C1 retained. Cancel keeps C1. Repeat Back /
|
||||
Resume, Discard, observe the second hidden interval, then Resume / complete4.
|
||||
B/key2 opens. Forward / complete5 opens C/key3 with C0, without discarded C1.
|
||||
Back / complete6 then Push A / complete7 creates A/key4 and truncates C's
|
||||
forward branch. Native Forward stays at A/key4. Initial A/key1 remains distinct.
|
||||
2. **Changed/cancelled departure during save.** Fresh tab, complete1, edit A1,
|
||||
Push B, Save and leave (write2). Replace C creates a fresh proposal. Replay
|
||||
prior Cancel must leave it intact. Commit2 records A1/version2 before Complete2;
|
||||
neither effect releases C. After Complete2, A is clean but C still needs the
|
||||
fresh Save and leave choice; it replaces entry1 with C, then complete3.
|
||||
In another fresh tab repeat A1/Push B/Save, then Cancel. Edit A2, Commit2 /
|
||||
Complete2: A2 remains dirty, saved revision1 and no departure. Push B, edit A3,
|
||||
Discard: stale departure is rejected; A3 remains.
|
||||
3. **Simulated session transitions and late outcomes.** Fresh tab, complete1,
|
||||
edit A1, Push B, Save and leave, Simulate expiry. Editor/approval clear and
|
||||
write2 becomes unknown. Simulate login A / X starts read3; complete it, Restore,
|
||||
Save: unresolved blocks replay. Commit2 / Complete2 updates the original ledger
|
||||
without certifying the restored editor; another Save stops at conflict.
|
||||
For overlapping owners, fresh tab: leave read1 pending, Push B (read2 pending),
|
||||
Push C / complete3, edit C1, manual Save (write4), Push B, switch B / X (read5).
|
||||
Complete1 and Fail2 must preserve loading5 and empty editor. Complete5, Restore
|
||||
reports no-draft. Edit Q1 and Save (write6). Commit4 / Complete4 must leave Q1,
|
||||
saving6 and current feedback intact. Native Back into the old epoch hides the
|
||||
editor; Resume restores B/X's current A entry without another read/approval.
|
||||
Commit6 / Complete6 certifies only Q1. A separate fresh A1/manual-save2 → switch
|
||||
B / complete3 → Q1/save4 sequence uses Fail2, then Commit2: old error/finalizer
|
||||
cannot clear saving4, and response loss did not cancel the synthetic old effect.
|
||||
|
||||
Ordinary controls are A0 read, manual A1 save without navigation, and clean Push B
|
||||
then native Back to original A/key1 with a fresh read. Each independent case starts
|
||||
from a fresh tab, not a reload/persistence assumption. Buttons Native Back/Forward
|
||||
also call the real History API; an automation API may instead reject a boundary
|
||||
traversal before issuing it. Record that distinction.
|
||||
|
||||
For the acknowledgement/restoration interleaving, use a fresh tab: complete1,
|
||||
Push B/complete2, edit B1, Push C, Save and leave (write3/proposal1), then actual
|
||||
Back to A. Before Resume, observe native A but committed B/key2, hidden editor
|
||||
and dialog, with new traversal proposal2 targeting A/key1. Commit3/Complete3
|
||||
must save B1/version2 while native A and entries A,B remain untouched; no C read
|
||||
or entry is created. Resume restores B/key2 and shows proposal2. Replay prior
|
||||
Cancel cannot cancel it. Save and leave on that fresh clean proposal stages a
|
||||
traversal; Resume/complete4 opens A/key1. Repeat proportionately with the older
|
||||
departure as Replace C or a traversal to A, and with acknowledgement after
|
||||
restoration is issued but before its pop, or after restoration. A newer B2 edit
|
||||
before Back stays dirty after the B1 acknowledgement and needs its own save.
|
||||
|
||||
## Regression and limits
|
||||
|
||||
Run `node --test tests/browser-workflow.test.mjs tests/combined-navigation.test.mjs`.
|
||||
The history port in Node tests is explicitly a transport **double**, not browser
|
||||
evidence. Five disposable controller faults must pass ordinary controls before
|
||||
named state/effect mismatches reject wrong restoration direction, edits during
|
||||
reconciliation, revived obsolete proposals, unchecked restored-entry keys and
|
||||
acknowledgement navigation before native source restoration.
|
||||
The existing integrated model's regressions remain separate.
|
||||
|
||||
For a meaningful browser distinction, make a disposable copy of the fixture and
|
||||
combined modules. In its app only, remove `blocked ||` from the expression assigning
|
||||
the editor panel's `hidden` property. Ordinary read/save/clean Back must still work.
|
||||
Dirty Back must then fail the DOM visibility expectation while native URL/view
|
||||
disagree; a "hidden" status string is insufficient. Keep the original candidate
|
||||
unchanged, bind the copy/origin/source hashes, retain the actual mismatch, and
|
||||
close/stop that copy. Harness exceptions are failures, not useful fault kills.
|
||||
|
||||
Same-author, source-exposed finite checks establish only the recorded browser,
|
||||
build and ordered procedures. They establish no original parity, model quality,
|
||||
real authentication/service enforcement, durable or secure retention, browser
|
||||
reload/unload persistence, arbitrary concurrent traversal, other-browser/device
|
||||
coverage, assistive-technology announcement or accessibility certification. The
|
||||
nonmodal departure region and native controls permit interaction; DOM roles and
|
||||
focus observations do not establish screen-reader behavior. Required browser
|
||||
checks remain unavailable if the existing authorized browser cannot load or
|
||||
interact with the local fixture; model/static passes cannot replace them.
|
||||
@@ -0,0 +1,63 @@
|
||||
import {createBrowserWorkflow} from './controller.mjs';
|
||||
const $=id=>document.getElementById(id);
|
||||
const run=crypto.randomUUID(),completed=new Set(),committed=new Set();
|
||||
let previous=null,lastId=null;
|
||||
const port={
|
||||
read:()=>({url:location.pathname,state:structuredClone(history.state)}),
|
||||
push:(state,url)=>history.pushState(state,'',url),
|
||||
replace:(state,url)=>history.replaceState(state,'',url),
|
||||
go:delta=>history.go(delta),
|
||||
onPop:callback=>addEventListener('popstate',()=>callback(port.read()))
|
||||
};
|
||||
const workflow=createBrowserWorkflow(port,render,run);
|
||||
function dispatch(action) {workflow.act(action);}
|
||||
function context(scope,active) {dispatch({type:'context',scope,active,read:active,write:active});}
|
||||
function render() {
|
||||
const record=workflow.snapshot(),s=record.model,entry=s.entries[s.index];
|
||||
const blocked=record.stopped || record.reconciliation !== null;
|
||||
$('identity').textContent=`SIMULATED ${s.active ? s.scope : 'expired'} · session ${s.session} · route lifetime ${s.lifetime}`;
|
||||
$('route').textContent=`Native URL ${record.native.url} · committed view ${entry.url} · entry ${entry.key} · native position ${record.pos} · epoch ${record.epoch}`;
|
||||
$('status').textContent=blocked ? (record.stopped ? 'Stopped: unsupported history event' : 'Reconciling native URL; editor hidden') : `${s.notice} · loading ${s.loading ?? 'none'} · saving ${s.saving ?? 'none'} · dirty ${s.dirty}`;
|
||||
$('editor-panel').hidden=blocked || !s.active || !s.draft;
|
||||
$('hidden').hidden=!$('editor-panel').hidden;
|
||||
$('resume').hidden=record.reconciliation === null || record.stopped;
|
||||
$('resume').disabled=record.reconciliation?.issued === true;
|
||||
if ($('editor').value !== (s.draft?.text ?? '')) $('editor').value=s.draft?.text ?? '';
|
||||
$('editor').disabled=blocked || !s.active || s.write !== true || !s.draft;
|
||||
$('revision').textContent=s.draft ? `Revision ${s.draft.revision}; saved revision ${s.draft.savedRevision}; remote base ${s.draft.base}` : '';
|
||||
for (const id of ['a','b','c','push-c','back','forward','save','restore','expire','owner-a','owner-b','stale']) $(id).disabled=blocked || (id === 'stale' && previous === null);
|
||||
$('review').hidden=blocked || !s.departure;
|
||||
if (s.departure) {
|
||||
const p=s.departure;
|
||||
$('proposal').textContent=`Proposal ${p.id}: ${p.kind} ${p.url}, entry ${p.key ?? 'new/current'}, source revision ${p.sourceRevision}, lifetime ${p.sourceLifetime}`;
|
||||
if (lastId !== p.id && !blocked) {
|
||||
if (lastId !== null) previous=lastId;
|
||||
lastId=p.id;$('cancel').focus();
|
||||
}
|
||||
} else if (lastId !== null) {
|
||||
previous=lastId;lastId=null;
|
||||
if ($('review').contains(document.activeElement)) $('save').focus();
|
||||
}
|
||||
$('stale').disabled=blocked || previous === null;
|
||||
$('operations').replaceChildren();
|
||||
for (const row of record.service.calls) {
|
||||
const span=document.createElement('p');span.textContent=`${row.kind} ${row.op}: ${row.scope} ${row.url} (session ${row.session}, lifetime ${row.lifetime}) `;
|
||||
const button=(label,fn,disabled=false)=>{const b=document.createElement('button');b.textContent=label;b.disabled=disabled;b.onclick=async()=>{try {await fn();} catch(e) {b.textContent='Fixture error: '+e.message;throw e;}};span.append(b);};
|
||||
if (row.kind === 'write') button(`Commit write ${row.op}`,()=>{committed.add(row.op);workflow.commit(row.op);},committed.has(row.op));
|
||||
button(`Complete ${row.kind} ${row.op}`,async()=>{completed.add(row.op);await workflow.finish(row.op);},completed.has(row.op) || (row.kind === 'write' && !committed.has(row.op)));
|
||||
button(`Fail ${row.kind} ${row.op}`,async()=>{completed.add(row.op);await workflow.finish(row.op,true);},completed.has(row.op));
|
||||
$('operations').append(span);
|
||||
}
|
||||
$('effects').textContent='Synthetic service effects: '+JSON.stringify(record.service.effects);
|
||||
$('observation').textContent=JSON.stringify(record,null,2);
|
||||
}
|
||||
for (const [id,kind,url] of [['a','push','a'],['b','push','b'],['c','replace','c'],['push-c','push','c']]) $(id).onclick=()=>dispatch({type:'navigate',kind,url:'/items/'+url});
|
||||
$('back').onclick=()=>history.back();$('forward').onclick=()=>history.forward();
|
||||
$('resume').onclick=()=>workflow.resume();
|
||||
$('editor').oninput=e=>dispatch({type:'edit',text:e.target.value});
|
||||
$('save').onclick=()=>dispatch({type:'save'});$('restore').onclick=()=>dispatch({type:'restore'});
|
||||
for (const [id,choice] of [['choose-save','save'],['discard','discard'],['cancel','cancel']]) $(id).onclick=()=>dispatch({type:'choose',id:workflow.snapshot().model.departure.id,choice});
|
||||
$('stale').onclick=()=>dispatch({type:'choose',id:previous,choice:'cancel'});
|
||||
$('expire').onclick=()=>context(workflow.snapshot().model.scope,false);
|
||||
$('owner-a').onclick=()=>context('a/x',true);$('owner-b').onclick=()=>context('b/x',true);
|
||||
render();
|
||||
@@ -0,0 +1,91 @@
|
||||
import {createWorkflow} from '../combined-navigation/candidate.mjs';
|
||||
import {controlledAdapter} from '../combined-navigation/double.mjs';
|
||||
|
||||
// Browser transport around the unchanged proposed model. The port is native in
|
||||
// app.mjs; Node regressions use an explicitly labelled history double.
|
||||
export function createBrowserWorkflow(port, changed=()=>{}, run='disposable-run') {
|
||||
const service=controlledAdapter(), events=[];
|
||||
let epoch=1, pos=0, reconciliation=null, stopped=false, sequence=0;
|
||||
const event=(type,details={})=>events.push({sequence:++sequence,type,...structuredClone(details)});
|
||||
const stamp=(s=model.snapshot())=>({fixture:'rea-browser-workflow',run,epoch,pos,index:s.index,key:s.entries[s.index].key});
|
||||
const owned=e=>e?.fixture === 'rea-browser-workflow' && e.run === run && Number.isSafeInteger(e.pos);
|
||||
const current=()=>model.snapshot();
|
||||
function fail(reason) {stopped=true;reconciliation=null;event('stopped',{reason});changed();}
|
||||
function history(row) {
|
||||
if (row.navigation === 'traverse') {
|
||||
const native=port.read();
|
||||
if (native.state?.epoch === epoch && native.state?.key === row.key && native.url === row.url) {
|
||||
pos=native.state.pos;event('accepted-pop',{key:row.key,url:row.url});
|
||||
} else {
|
||||
const s=current(),delta=row.index-(native.state?.index ?? NaN);
|
||||
if (!owned(native.state) || native.state.epoch !== epoch || !Number.isSafeInteger(delta) || delta === 0) return fail('unavailable-traversal');
|
||||
reconciliation={kind:'commit',target:{...stamp(s),pos:native.state.pos+delta},url:row.url};
|
||||
event('traversal-planned',{delta,key:row.key,url:row.url});
|
||||
}
|
||||
} else {
|
||||
if (row.navigation === 'push') {pos++;port.push(stamp(),row.url);}
|
||||
else port.replace(stamp(),row.url);
|
||||
event('native-'+row.navigation,{...port.read()});
|
||||
}
|
||||
}
|
||||
const model=createWorkflow({...service,history});
|
||||
port.replace(stamp(),'/items/a');event('initial-entry',port.read());
|
||||
port.onPop(native=>{
|
||||
// URL has ALREADY moved here. Record it before restoring or accepting it.
|
||||
event('pop-observed',{...native,committedUrl:current().entries[current().index].url,hidden:true});
|
||||
if (stopped) return;
|
||||
if (!owned(native.state)) return fail('foreign-entry');
|
||||
if (reconciliation) {
|
||||
const pending=reconciliation;
|
||||
if (native.state.pos !== pending.target.pos || native.state.epoch !== pending.target.epoch || native.state.key !== pending.target.key || native.url !== pending.url) return fail('unexpected-pop-during-reconciliation');
|
||||
reconciliation=null;pos=native.state.pos;
|
||||
event('reconciled',{kind:pending.kind,...native});
|
||||
changed();return;
|
||||
}
|
||||
const s=current(),entry=s.entries[s.index],source={...stamp(s)};
|
||||
const target=s.entries[native.state.index];
|
||||
const obsolete=native.state.epoch !== epoch;
|
||||
if (!obsolete && (!target || target.key !== native.state.key || target.url !== native.url || native.state.pos-pos !== native.state.index-s.index)) return fail('entry-identity-mismatch');
|
||||
const proposal={type:'traverse',delta:native.state.index-s.index};
|
||||
if (obsolete || s.dirty || s.saving !== null || s.outcomes.some(r=>['pending','unknown'].includes(r.status)) || !s.active) {
|
||||
reconciliation={kind:'restore',target:source,url:entry.url,proposal:obsolete ? null : proposal};
|
||||
// Register this exact native request before any pending acknowledgement can
|
||||
// release an older approval. The model guards it; the dialog stays hidden
|
||||
// until the source entry is restored. Do not request it again on Resume.
|
||||
if (reconciliation.proposal) model.dispatch(reconciliation.proposal);
|
||||
event('restoration-issued',{delta:pos-native.state.pos,obsolete,source,observed:native.state});
|
||||
// A visible operator gate freezes this real intermediate DOM state. It
|
||||
// controls when history.go runs; it does not emulate the browser's pop.
|
||||
changed();
|
||||
} else {model.dispatch(proposal);changed();}
|
||||
});
|
||||
function act(action) {
|
||||
if (stopped || reconciliation) return false;
|
||||
model.dispatch(action);
|
||||
if (action.type === 'context') {
|
||||
epoch++;port.replace(stamp(),'/items/a');event('context-entry',port.read());
|
||||
if (action.active && action.read === true) model.dispatch({type:'read'});
|
||||
}
|
||||
event('action',{action});changed();return true;
|
||||
}
|
||||
model.dispatch({type:'read'});
|
||||
return {
|
||||
act,
|
||||
resume() {
|
||||
if (stopped || !reconciliation || reconciliation.issued) return false;
|
||||
reconciliation.issued=true;
|
||||
const delta=reconciliation.target.pos-port.read().state.pos;
|
||||
event('reconciliation-issued',{delta});port.go(delta);changed();return true;
|
||||
},
|
||||
commit(op) {service.commit(op);event('service-commit',{op});changed();},
|
||||
async finish(op,failed=false) {
|
||||
const row=service.inspect().calls.find(r=>r.op === op);
|
||||
if (!row) throw Error('Unknown browser operation');
|
||||
const effects=service.inspect().effects.filter(e=>e.kind === 'write' && e.scope === row.scope && e.url === row.url);
|
||||
const last=effects.at(-1),text=last?.text ?? (row.scope === 'a/x' ? row.url.at(-1).toUpperCase()+'0' : 'Q0');
|
||||
service.finish(op,{text,version:last?.version ?? 1},failed);
|
||||
await model.settle(op);event('response',{op,failed});changed();
|
||||
},
|
||||
snapshot() {return structuredClone({model:current(),native:port.read(),epoch,pos,reconciliation,stopped,events,service:service.inspect()});}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"frozenAt": "2026-10-23T18:41:03Z",
|
||||
"basis": "Same-author separately reasoned proposed contract, frozen before browser adapter implementation; no original-app parity claim",
|
||||
"ordinary": ["Initial /items/a read A0; clean push /items/b reads B0; native Back selects original a entry with fresh read", "Edit A1; manual save captures revision 1; commit then acknowledge makes saved revision 1 without navigation"],
|
||||
"families": {
|
||||
"history": ["a -> b -> c creates distinct native entries", "dirty C1 then native Back first observes /items/b while committed view is /items/c; editor is hidden during reconciliation", "restore the original c entry before showing exact b traversal proposal; Cancel keeps c/C1 without write", "repeat Back and Discard opens original b entry; Forward opens original c entry with a fresh read, without restoring discarded C1", "Back then push a truncates forward c; duplicate a URLs have distinct entry keys"],
|
||||
"departure": ["dirty A1; propose push b; Save captures payload/revision/proposal", "replace departure by replace c while write pending; replay old cancel leaves current proposal intact", "commit/ack old save records A1 version2 but stays a and cannot release c", "fresh Save choice on clean revision replaces current entry with c, retaining its key", "cancel departure during save; new edit A2; commit/ack captured A1 leaves A2 dirty and never navigates", "fresh departure then edit again invalidates non-cancel choice"],
|
||||
"session": ["expire during save clears editor/proposal, marks write unknown and replaces current native entry with new epoch", "re-login same owner starts fresh read; explicit Restore recovers A1 but Save stays unresolved", "late old write commits/acks its original ledger; it cannot certify current editor or change its loading/saving; replay stays blocked by newer receipt/base conflict", "switch to b/x with old reads/writes pending; old read success/error/finalizer and old write success/error cannot change b/x editor/status/pending operations", "Back into obsolete a/x epoch restores current b/x entry without read, editor leak or old approval"]
|
||||
},
|
||||
"limits": ["one disposable tab/origin per independent case", "no real authentication, persistence, reload/unload guarantees, service enforcement or assistive-technology claim", "unexpected foreign entry fails closed; browser traversal is reconciliation, not pre-commit veto"]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
||||
<title>Simulated browser workflow lab</title>
|
||||
<style>
|
||||
body{font:16px system-ui;max-width:1000px;margin:24px auto;padding:0 20px;background:#f5f7fa;color:#17263b}h1{font-size:26px}section{background:white;border:1px solid #bccada;border-radius:8px;padding:16px;margin:12px 0}button{font:inherit;padding:8px;margin:4px;border-radius:5px;border:1px solid #687c94;background:#eef3f9;color:#17263b}button:focus-visible,textarea:focus-visible{outline:3px solid #ac3b00;outline-offset:2px}textarea{display:block;width:95%;min-height:70px;font:inherit}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}#review{border:3px solid #ac3b00}#identity{font-weight:bold}small{display:block}code{overflow-wrap:anywhere}
|
||||
</style>
|
||||
<h1>Simulated browser workflow lab</h1>
|
||||
<p><strong>Authentication and service are visibly simulated.</strong> All accounts, grants, data and commits below are synthetic and volatile. Use one disposable tab per case. No reload/unload, secure retention or real service guarantee.</p>
|
||||
<section><div id="identity"></div><p id="route"></p><p id="status" role="status" aria-live="polite"></p>
|
||||
<nav aria-label="Fixture destinations"><button id="a">Push A</button><button id="b">Push B</button><button id="c">Replace C</button><button id="push-c">Push C</button><button id="back">Native Back</button><button id="forward">Native Forward</button></nav>
|
||||
<div id="editor-panel"><label for="editor">Current owner's editor</label><textarea id="editor"></textarea><p id="revision"></p><button id="save">Save current draft</button><button id="restore">Restore retained draft</button></div>
|
||||
<p id="hidden" hidden>Editor hidden while URL and view reconcile or access is inactive.</p>
|
||||
<button id="resume" hidden>Resume URL reconciliation</button>
|
||||
<section id="review" role="dialog" aria-labelledby="review-title" hidden><h2 id="review-title">Departure review (nonmodal)</h2><p id="proposal"></p><button id="choose-save">Save and leave</button><button id="discard">Discard and leave</button><button id="cancel">Cancel departure</button></section>
|
||||
<button id="stale" disabled>Replay prior Cancel choice</button></section>
|
||||
<section><h2>Simulated session controls</h2><button id="expire">Simulate expiry</button><button id="owner-a">Simulate login A / X</button><button id="owner-b">Simulate switch B / X</button></section>
|
||||
<section><h2>Fixture operator: controlled responses</h2><small>Operator instrumentation deliberately shows synthetic old operations/effects. This is separate from the current owner's editor; it is not an authentication boundary.</small><div id="operations"></div><pre id="effects"></pre></section>
|
||||
<details><summary>Rendered observation record</summary><pre id="observation"></pre></details>
|
||||
<script type="module" src="/evals/browser-workflow/app.mjs"></script></html>
|
||||
@@ -0,0 +1,20 @@
|
||||
// Dependency-free loopback-only server, explicit public fixture allowlist.
|
||||
import http from 'node:http';
|
||||
import fs from 'node:fs';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
const root=new URL('../../',import.meta.url);
|
||||
const allowed=new Set(['evals/browser-workflow/index.html','evals/browser-workflow/app.mjs','evals/browser-workflow/controller.mjs','evals/combined-navigation/candidate.mjs','evals/combined-navigation/double.mjs']);
|
||||
export function createServer() {
|
||||
return http.createServer((req,res)=>{
|
||||
if (req.method !== 'GET' || !/^127\.0\.0\.1:\d+$/.test(req.headers.host ?? '')) {res.writeHead(403);res.end('Loopback GET only');return;}
|
||||
const url=new URL(req.url,'http://127.0.0.1');
|
||||
const file=/^\/items\/[abc]$/.test(url.pathname) ? 'evals/browser-workflow/index.html' : url.pathname.slice(1);
|
||||
if (url.search || !allowed.has(file)) {res.writeHead(404);res.end('Fixture route not found');return;}
|
||||
const body=fs.readFileSync(new URL(file,root));
|
||||
res.writeHead(200,{'Content-Type':file.endsWith('.html') ? 'text/html; charset=utf-8' : 'text/javascript; charset=utf-8','Cache-Control':'no-store','Content-Security-Policy':"default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; connect-src 'none'; img-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",'X-Content-Type-Options':'nosniff'});res.end(body);
|
||||
});
|
||||
}
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const server=createServer();server.listen(0,'127.0.0.1',()=>console.log(JSON.stringify({pid:process.pid,origin:`http://127.0.0.1:${server.address().port}`,entry:'/items/a',scope:'task-owned disposable fixture'})));
|
||||
for (const signal of ['SIGINT','SIGTERM']) process.on(signal,()=>server.close(()=>process.exit(0)));
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
# Offline clipboard example
|
||||
|
||||
Run `node --test tests/clipboard.test.mjs` from the repository root. No dependency,
|
||||
network, model call, real clipboard or user document is used. The only clipboard
|
||||
is the disposable recording/failure double in `double.mjs`.
|
||||
|
||||
One proposed teaching policy covers three families: copy/paste insertion or
|
||||
replacement, cut with failure retention, and undo/identity boundaries. The same
|
||||
author separately reasoned and froze 12 sequences / 30 literal intermediate
|
||||
states in `expectations.json` and its SHA-256 identity before candidate adaptation.
|
||||
Source is exposed; this is not independently authored evidence, original parity
|
||||
or a model instruction-following test.
|
||||
|
||||
## Finite policy
|
||||
|
||||
Two synthetic in-memory documents have globally unique string IDs/text values;
|
||||
initial IDs exclude the generated `n<number>` namespace. Only declared finite
|
||||
copy/cut/paste/edit/undo/redo actions are supported: no document replacement,
|
||||
owner lifecycle, parser or arbitrary object API. Copy/cut captures selected
|
||||
items in document order. The double clones on read/write and independently
|
||||
records attempted operations and rejection. Write rejection retains its old
|
||||
payload. The checker reads the double separately from candidate snapshots.
|
||||
|
||||
The representation is plain text (one item) or a version-1 structured fragment
|
||||
of one to three distinct ID/text pairs, with strings limited to 64 UTF-16 code
|
||||
units. Empty text is valid. Unexpected fields, unsupported versions, duplicate
|
||||
IDs and malformed later items reject before edits. These are finite object-shape
|
||||
checks, not hostile-byte parsing or rich HTML sanitization. Markup-looking plain
|
||||
text stays a value; no renderer executes it.
|
||||
|
||||
Paste names an owner, index and exact contiguous IDs to replace at that index.
|
||||
Copy/text paste uses captured values and fresh IDs on each accepted paste. Cut
|
||||
leaves the source intact until an accepted cross-document paste. Its local
|
||||
ticket binds the full source snapshot and captured payload. Changed source,
|
||||
foreign/forged move, same-document move, invalid destination, read rejection or
|
||||
invalid format leaves both documents/history unchanged. The ticket is an
|
||||
in-process teaching mechanism, not a platform capability or cross-app protocol.
|
||||
No source revision/generation or concurrency guarantee exists: identical
|
||||
restored source content is considered applicable under this local policy.
|
||||
|
||||
A move retains IDs and creates one history entry for both documents. Accepted
|
||||
edits/pastes clear redo; copy/cut/rejection do not. Undo/redo restore exact values,
|
||||
IDs/order without touching clipboard contents. Consumed cut tickets stay consumed
|
||||
through undo; repeated paste rejects and redo retains the moved IDs. Moving again
|
||||
requires a new cut. Allocation is monotonic and outside history. These are local
|
||||
choices, not universal editor semantics. Snapshots report history depth/status
|
||||
alongside actual document data; no saved-file persistence is implied.
|
||||
|
||||
## Checks and limits
|
||||
|
||||
Twelve faults each pass four ordinary checkpoints before a specific assertion
|
||||
fails: selection order, insertion instead of replacement, removal on failed
|
||||
cut write, partial invalid paste, stale source removal, wrong destination
|
||||
selection, copying live values, undo/redo recreating IDs, undo clearing clipboard,
|
||||
cut reuse after undo and foreign deletion authority. Exceptions do not count as
|
||||
kills. Checks also cover detached snapshots/double values, representation limits,
|
||||
direct rejection, comparator field sensitivity and concealed clipboard effects.
|
||||
|
||||
No real OS/browser permissions, focus/gesture behavior, native/rich formats,
|
||||
cross-app interoperability, durability, concurrent editing, process crash,
|
||||
clipboard replacement race or saved-file behavior is tested. Only synchronous
|
||||
known rejection is injected; response-loss/partial effects are outside the example.
|
||||
Local history cannot reverse external effects. Use the [focused guidance](../../skills/rea-reconstruction-companion/references/clipboard.md)
|
||||
for the actual target. Standalone installations omit these repository-only
|
||||
files. Historical fixtures and oracles remain unchanged.
|
||||
@@ -0,0 +1,75 @@
|
||||
import {isDeepStrictEqual as equal} from 'node:util';
|
||||
|
||||
// Finite proposed teaching policy, not an original editor or platform adapter.
|
||||
export function createEditor(initial, clipboard) {
|
||||
let docs = structuredClone(initial), status = 'ready', nextId = 1, nextToken = 1;
|
||||
const undo = [], redo = [], pending = new Map(), consumed = new Set();
|
||||
const snapshot = () => structuredClone({docs, history:[undo.length,redo.length], status});
|
||||
const validText = text => typeof text === 'string' && text.length <= 64;
|
||||
const keys = (value, expected) => value && typeof value === 'object' && !Array.isArray(value) && equal(Object.keys(value).sort(),expected.sort());
|
||||
function decode(value) {
|
||||
if (keys(value,['format','text']) && value.format === 'text' && validText(value.text)) return {items:[[null,value.text]]};
|
||||
if (!value || !keys(value,value.move === undefined ? ['format','version','items'] : ['format','version','items','move']) ||
|
||||
value.format !== 'items' || value.version !== 1 || !Array.isArray(value.items) || value.items.length < 1 || value.items.length > 3) return null;
|
||||
if (!value.items.every(item => Array.isArray(item) && item.length === 2 && typeof item[0] === 'string' && item[0].length > 0 && item[0].length <= 64 && validText(item[1]))) return null;
|
||||
if (new Set(value.items.map(item => item[0])).size !== value.items.length) return null;
|
||||
if (value.move !== undefined && (!keys(value.move,['token','source']) || !Number.isSafeInteger(value.move.token) || typeof value.move.source !== 'string')) return null;
|
||||
return value;
|
||||
}
|
||||
function commit(before) { undo.push({before,after:structuredClone(docs)}); redo.length = 0; }
|
||||
function dispatch(action) {
|
||||
switch (action.type) {
|
||||
case 'copy': case 'cut': {
|
||||
const source = docs[action.owner];
|
||||
if (!source || !Array.isArray(action.ids) || action.ids.length < 1 || new Set(action.ids).size !== action.ids.length || action.ids.some(id => !source.some(item => item[0] === id))) { status = 'rejected'; break; }
|
||||
// Source document order wins over selection enumeration order.
|
||||
const items = source.filter(item => action.ids.includes(item[0]));
|
||||
const value = {format:'items',version:1,items:structuredClone(items)};
|
||||
if (action.type === 'cut') value.move = {token:nextToken++,source:action.owner};
|
||||
if (!decode(value)) { status = 'rejected'; break; }
|
||||
try { clipboard.write(value); }
|
||||
catch (error) { if (error.code !== 'CLIPBOARD_REJECTED') throw error; status = 'clipboard-rejected'; break; }
|
||||
if (action.type === 'cut') pending.set(value.move.token,{value:structuredClone(value),source:structuredClone(source)});
|
||||
status = action.type === 'cut' ? 'cut-pending' : 'copied';
|
||||
break;
|
||||
}
|
||||
case 'paste': {
|
||||
let value;
|
||||
try { value = clipboard.read(); }
|
||||
catch (error) { if (error.code !== 'CLIPBOARD_REJECTED') throw error; status = 'clipboard-rejected'; break; }
|
||||
const fragment = decode(value), destination = docs[action.owner];
|
||||
if (!fragment || !destination || !Number.isInteger(action.at) || action.at < 0 || action.at > destination.length || !Array.isArray(action.replace) ||
|
||||
!equal(destination.slice(action.at,action.at + action.replace.length).map(item => item[0]),action.replace)) { status = 'rejected'; break; }
|
||||
const move = fragment.move, ticket = move && pending.get(move.token);
|
||||
// An untrusted clipboard cannot mint local deletion authority.
|
||||
if (move && (!ticket || consumed.has(move.token) || !equal(ticket.value,fragment) || !equal(ticket.source,docs[move.source]) || move.source === action.owner)) { status = 'rejected'; break; }
|
||||
const before = structuredClone(docs);
|
||||
const inserted = move ? structuredClone(fragment.items) : fragment.items.map(item => ['n'+nextId++,item[1]]);
|
||||
if (move) docs[move.source] = docs[move.source].filter(item => !fragment.items.some(selected => selected[0] === item[0]));
|
||||
destination.splice(action.at,action.replace.length,...inserted);
|
||||
if (move) consumed.add(move.token);
|
||||
commit(before); status = move ? 'moved' : 'pasted';
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
const item = docs[action.owner]?.find(item => item[0] === action.id);
|
||||
if (!item || !validText(action.text)) { status = 'rejected'; break; }
|
||||
if (item[1] === action.text) { status = 'unchanged'; break; }
|
||||
const before = structuredClone(docs); item[1] = action.text; commit(before); status = 'edited'; break;
|
||||
}
|
||||
case 'undo': {
|
||||
const entry = undo.pop();
|
||||
if (!entry) { status = 'unchanged'; break; }
|
||||
docs = structuredClone(entry.before); redo.push(entry); status = 'undone'; break;
|
||||
}
|
||||
case 'redo': {
|
||||
const entry = redo.pop();
|
||||
if (!entry) { status = 'unchanged'; break; }
|
||||
docs = structuredClone(entry.after); undo.push(entry); status = 'redone'; break;
|
||||
}
|
||||
default: throw Error('Unsupported finite action');
|
||||
}
|
||||
return snapshot();
|
||||
}
|
||||
return {snapshot,dispatch};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import fs from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
import {recordingClipboard} from './double.mjs';
|
||||
import {createEditor} from './candidate.mjs';
|
||||
export function frozen() {
|
||||
const bytes = fs.readFileSync(new URL('./expectations.json',import.meta.url));
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('./identity.json',import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256) throw Error('Changed frozen oracle');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export function compare(factory = createEditor, cases = null) {
|
||||
const oracle = frozen(), rows = [];
|
||||
for (const scenario of cases ?? [...oracle.ordinary,...oracle.boundaries]) {
|
||||
const clipboard = recordingClipboard({value:scenario.clipboard,failRead:scenario.failRead,failWrite:scenario.failWrite});
|
||||
const editor = factory(structuredClone(scenario.initial),clipboard);
|
||||
for (const [step,entry] of scenario.steps.entries()) {
|
||||
const actual = editor.dispatch(structuredClone(entry.action));
|
||||
// Observe the double independently; a candidate snapshot cannot conceal effects.
|
||||
const effects = clipboard.inspect();
|
||||
rows.push({id:scenario.id,step,expected:entry.expected,actual,effects,expectedEffects:entry.clipboard,
|
||||
passed:isDeepStrictEqual(actual,entry.expected) && isDeepStrictEqual(effects,entry.clipboard)});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Disposable in-memory clipboard double. No OS/browser clipboard API or I/O.
|
||||
export function recordingClipboard({value = null, failRead = false, failWrite = false} = {}) {
|
||||
let payload = structuredClone(value);
|
||||
const events = [];
|
||||
const reject = () => { const error = new Error('Injected clipboard rejection'); error.code = 'CLIPBOARD_REJECTED'; throw error; };
|
||||
return {
|
||||
read() { events.push(failRead ? 'read:rejected' : 'read:ok'); if (failRead) reject(); return structuredClone(payload); },
|
||||
write(value) { events.push(failWrite ? 'write:rejected' : 'write:ok'); if (failWrite) reject(); payload = structuredClone(value); },
|
||||
inspect() { return structuredClone({value:payload, events}); },
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"profile": "proposed-offline-clipboard-v1",
|
||||
"authorship": "Same author; source exposed; literal intermediate expectations reasoned and frozen before candidate adaptation.",
|
||||
"expectationsSha256": "5424bd4731ed6db772866adeb7939fdd900e5d2de041fde12437e89f69bacc06"
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# Combined navigation, edits and session recovery
|
||||
|
||||
Run with existing Node.js 20+, without dependencies, credentials or services:
|
||||
|
||||
```sh
|
||||
node evals/combined-navigation/check.mjs
|
||||
node --test tests/combined-navigation.test.mjs
|
||||
```
|
||||
|
||||
This **same-author, source-exposed proposed composition** uses one state/effect
|
||||
lifecycle across departure during save, expiry and explicit same-owner recovery,
|
||||
and account switching while reads/writes finish. Twenty sequences freeze **189
|
||||
complete literal checkpoints** with independent calls, service commits and history
|
||||
effects. Twenty-two disposable source faults first pass **15 ordinary checkpoints**,
|
||||
then fail a named state/effect assertion. The **34 focused tests** also cover
|
||||
workspace/resource isolation, same-URL lifetimes, old success during a new owner's
|
||||
save, manual-save approval, unknown grants, conditional service conflict and
|
||||
response loss before a later commit. Exceptions never count as fault detections.
|
||||
|
||||
Expected declarations were reasoned and expanded into full literal JSON before
|
||||
candidate adaptation; no candidate output or action-to-transition simulator
|
||||
generated the oracle. Two initial re-login checkpoints omitted immediately readable
|
||||
same-owner unknown outcomes. The original freeze/failed run remain private;
|
||||
`identity.json` records the explicit corrections and original hash. One mutation
|
||||
test initially named a nonexistent traversal checkpoint; its index was corrected
|
||||
without changing the candidate/contract. These are author corrections, not
|
||||
independent acceptance, original parity or evidence of model quality.
|
||||
|
||||
## Composition findings and proposed choices
|
||||
|
||||
The independent examples remain unchanged. Their policies differ:
|
||||
|
||||
| Existing bounded profile | Composition decision here |
|
||||
| --- | --- |
|
||||
| Navigation rejects replacement targets and omits save. | Each departure request, including repetition, supersedes the proposal with a fresh ID. |
|
||||
| Unsaved navigation replaces targets and guards an in-memory commit. | Approval binds exact proposal/revision; external commit is independent of local applicability. |
|
||||
| Session recovery retains scoped drafts for one document. | Ownership includes account/workspace and resource, with distinct session and route lifetimes. |
|
||||
|
||||
These choices are proposed, not recovered target behavior or universal improvements.
|
||||
The narrow grammar is `/items/a`, `/items/b` or `/items/c`. Volatile entries have
|
||||
keys; push truncates the forward branch, replace retains the current key, traversal
|
||||
selects an existing key. Same-current-URL push/replace is a no-op, leaving any old
|
||||
proposal unchanged. Distinct entries can share a URL. Each committed navigation
|
||||
creates a route lifetime, clears display/editor and starts a controlled read.
|
||||
The guard runs **before** modeled entry movement; it proves no browser traversal
|
||||
veto, URL reconciliation, native history, reload or unload behavior.
|
||||
|
||||
1. Context invalidation wins over departure/display/feedback. It creates fresh
|
||||
session/route lifetimes, resets volatile history, clears the editor and marks
|
||||
pending writes unknown. Drafts/outcomes remain owner/resource scoped; outcomes
|
||||
are hidden without active read access. Synthetic context/grants are no trusted auth.
|
||||
2. Departure binds ID, source lifetime/revision and exact kind/URL/entry. An old
|
||||
choice cannot cancel/release its replacement. New edits invalidate an old
|
||||
non-cancel choice. Cancel drops navigation only. Discard removes the retained
|
||||
current draft and is blocked by unresolved writes.
|
||||
3. Save captures owner/session/route, payload/revision, remote base and approved
|
||||
proposal ID. Equal-text edit is a no-op; other edits increment revision,
|
||||
including editing back to old text. Dirty is revision-based. Acknowledgement
|
||||
certifies only the captured saved revision; newer text stays dirty. Departure
|
||||
also needs the same proposal and a clean latest draft. Manual save does not
|
||||
approve a later departure; save-choice during a write stops as unresolved.
|
||||
4. Local invalidation cannot cancel service work. A late receipt updates its
|
||||
original ledger without changing another lifetime's editor, feedback, history
|
||||
or loading/saving. Service commit may precede acknowledgement, follow expiry/
|
||||
account switching, or occur even after response loss.
|
||||
5. Recovery requires a fresh permitted same-owner read, then explicit restore into
|
||||
a clean editor. Other account/workspace/resource cannot inherit the draft.
|
||||
Restore does not send or mark saved. Pending/unknown writes block replay.
|
||||
Changed remote version, even with equal text, or a known receipt newer than the
|
||||
restored base blocks overwrite. No receipt lookup, automatic merge/replay or
|
||||
repair is supplied; these conservative stops remain unresolved.
|
||||
|
||||
The independent adapter records calls/effects and checks synthetic write grants
|
||||
and expected remote version at commit. Known denial/conflict has no write effect;
|
||||
response loss is unknown. Versions start at 1 per owner/resource and increment on
|
||||
modeled writes. Read completions are separately supplied fixture observations,
|
||||
not queries against that map; consistency with service state is a scenario
|
||||
assumption. A supplemental test independently advances the service to exercise a
|
||||
conditional conflict. Arbitrary readback/concurrent-writer correctness is unproven.
|
||||
|
||||
Only volatile maps, controlled real promises and recorded adapter effects are
|
||||
exercised. Maps retain finite-run records without production eviction. There is
|
||||
no real persistence, browser/router, focus/dialog/accessibility, authentication,
|
||||
authorization, secure retention, distributed exactly-once delivery, original parity
|
||||
or model benchmark. Local guards prove neither service cancellation nor rollback.
|
||||
Standalone installations omit evals/tests; use the portable
|
||||
[navigation](../../skills/rea-reconstruction-companion/references/navigation-history.md),
|
||||
[unsaved work](../../skills/rea-reconstruction-companion/references/unsaved-navigation.md)
|
||||
and [session recovery](../../skills/rea-reconstruction-companion/references/session-permissions.md)
|
||||
guidance to recover a selected target's actual contract.
|
||||
@@ -0,0 +1,132 @@
|
||||
// Finite proposed composition. Context/grants are synthetic, never trusted auth.
|
||||
export function createWorkflow(adapter) {
|
||||
const state={scope:'a/x',session:1,active:true,read:true,write:true,lifetime:1,
|
||||
entries:[{key:1,url:'/items/a'}],index:0,data:null,draft:null,
|
||||
loading:null,saving:null,departure:null,notice:'ready'};
|
||||
const retained=new Map(),ledgers=new Map(),operations=new Map();
|
||||
let nextOperation=1,nextDeparture=1,nextKey=2;
|
||||
const url=()=>state.entries[state.index].url;
|
||||
const ownerKey=(scope=state.scope,resource=url())=>JSON.stringify([scope,resource]);
|
||||
const ledger=(key=ownerKey())=>{if (!ledgers.has(key)) ledgers.set(key,[]);return ledgers.get(key);};
|
||||
const readable=()=>state.active && state.read === true;
|
||||
const writable=()=>readable() && state.write === true;
|
||||
const dirty=()=>state.draft !== null && state.draft.revision !== state.draft.savedRevision;
|
||||
const unresolved=()=>ledger().some(row=>['pending','unknown'].includes(row.status));
|
||||
const blocked=()=>{state.notice='blocked';};
|
||||
function capture() {return {op:nextOperation++,scope:state.scope,session:state.session,lifetime:state.lifetime,url:url()};}
|
||||
function applicable(c) {return readable() && c.scope === state.scope && c.session === state.session && c.lifetime === state.lifetime && c.url === url();}
|
||||
function startRead() {
|
||||
const c=capture();state.loading=c.op;state.notice='loading';
|
||||
const chain=adapter.read(c).then(value=>{
|
||||
if (value !== null && (typeof value?.text !== 'string' || !Number.isSafeInteger(value.version) || value.version < 1)) throw Error('Invalid fixture read');
|
||||
if (!applicable(c)) return;
|
||||
state.data=structuredClone(value);
|
||||
state.draft=value === null ? null : {text:value.text,revision:0,savedRevision:0,base:value.version};
|
||||
state.notice=value === null ? 'missing' : 'loaded';
|
||||
},()=>{if (applicable(c)) state.notice='read-error';}).finally(()=>{
|
||||
if (applicable(c) && state.loading === c.op) state.loading=null;
|
||||
});operations.set(c.op,chain);
|
||||
}
|
||||
function commitNavigation(intent) {
|
||||
// Called only after the proposal's revision/lifetime and decision are checked.
|
||||
if (intent.kind === 'traverse') state.index=state.entries.findIndex(row=>row.key === intent.key);
|
||||
else if (intent.kind === 'replace') state.entries[state.index]={key:state.entries[state.index].key,url:intent.url};
|
||||
else {state.entries.splice(state.index+1);state.entries.push({key:nextKey++,url:intent.url});state.index++;}
|
||||
state.lifetime++;state.data=null;state.draft=null;state.loading=null;state.saving=null;state.departure=null;
|
||||
const entry=state.entries[state.index];
|
||||
adapter.history({navigation:intent.kind,key:entry.key,url:entry.url,index:state.index,scope:state.scope,session:state.session});
|
||||
startRead();state.notice='navigated';
|
||||
}
|
||||
function startWrite(departure=null) {
|
||||
if (!writable() || !state.draft) {blocked();return;}
|
||||
if (unresolved()) {state.notice='unresolved';return;}
|
||||
if (state.data?.version !== state.draft.base || ledger().some(row=>row.status === 'saved' && row.version > state.draft.base)) {state.notice='conflict';return;}
|
||||
if (!dirty()) {if (departure) commitNavigation(departure);return;}
|
||||
const c={...capture(),text:state.draft.text,revision:state.draft.revision,base:state.draft.base,departure:departure?.id ?? null};
|
||||
const record={op:c.op,url:c.url,text:c.text,revision:c.revision,base:c.base,status:'pending',version:null};
|
||||
ledger().push(record);state.saving=c.op;state.notice='saving';
|
||||
const chain=adapter.write(c).then(value=>{
|
||||
if (!['saved','denied','conflict'].includes(value?.status) ||
|
||||
(value.status !== 'denied' && (!Number.isSafeInteger(value.version) || value.version < 1)) ||
|
||||
(value.status === 'denied' && value.version !== null)) throw Error('Invalid fixture receipt');
|
||||
record.status=value.status;record.version=value.version;
|
||||
if (!applicable(c) || !writable()) return;
|
||||
if (value.status !== 'saved') {state.notice=value.status;return;}
|
||||
state.data={text:c.text,version:value.version};
|
||||
state.draft.savedRevision=c.revision;state.draft.base=value.version;
|
||||
retained.set(ownerKey(),structuredClone(state.draft));
|
||||
state.notice=dirty() ? 'older-save' : 'saved';
|
||||
const intent=state.departure;
|
||||
if (!dirty() && intent?.id === c.departure && intent.sourceLifetime === state.lifetime && intent.sourceRevision === state.draft.revision) commitNavigation(intent);
|
||||
},()=>{
|
||||
record.status='unknown';record.version=null;
|
||||
if (applicable(c)) state.notice='unknown';
|
||||
}).finally(()=>{
|
||||
if (applicable(c) && state.saving === c.op) state.saving=null;
|
||||
});operations.set(c.op,chain);
|
||||
}
|
||||
function request(intent) {
|
||||
if (!readable()) {blocked();return;}
|
||||
if (intent.kind !== 'traverse' && intent.url === url()) {state.notice='unchanged';return;}
|
||||
if (dirty() || state.saving !== null || unresolved()) {
|
||||
state.departure={id:nextDeparture++,...intent,sourceLifetime:state.lifetime,sourceRevision:state.draft?.revision ?? null};
|
||||
state.notice='review';return;
|
||||
}
|
||||
commitNavigation(intent);
|
||||
}
|
||||
return {
|
||||
dispatch(action) {
|
||||
switch(action.type) {
|
||||
case 'context':
|
||||
if (typeof action.scope !== 'string' || typeof action.active !== 'boolean') throw Error('Invalid fixture context');
|
||||
// Invalidation never cancels work at the service, including writes.
|
||||
for (const rows of ledgers.values()) for (const row of rows) if (row.status === 'pending') row.status='unknown';
|
||||
state.scope=action.scope;state.session++;state.lifetime++;
|
||||
state.active=action.active;state.read=action.read;state.write=action.write;
|
||||
state.entries=[{key:1,url:'/items/a'}];state.index=0;nextKey=2;
|
||||
state.data=null;state.draft=null;state.loading=null;state.saving=null;state.departure=null;state.notice='context-changed';break;
|
||||
case 'read':
|
||||
if (!readable() || state.loading !== null || state.saving !== null || state.draft !== null) {blocked();break;}
|
||||
startRead();break;
|
||||
case 'edit':
|
||||
if (!writable() || !state.draft) {blocked();break;}
|
||||
if (typeof action.text !== 'string') throw Error('Invalid fixture edit');
|
||||
if (action.text === state.draft.text) break;
|
||||
state.draft.text=action.text;state.draft.revision++;
|
||||
retained.set(ownerKey(),structuredClone(state.draft));state.notice='edited';break;
|
||||
case 'save':startWrite();break;
|
||||
case 'restore': {
|
||||
if (!readable() || !state.draft || dirty() || state.loading !== null || state.saving !== null) {blocked();break;}
|
||||
const draft=retained.get(ownerKey());
|
||||
if (!draft) {state.notice='no-draft';break;}
|
||||
state.draft=structuredClone(draft);state.notice='restored';break;
|
||||
}
|
||||
case 'navigate':
|
||||
if (!['push','replace'].includes(action.kind)) throw Error('Invalid fixture navigation kind');
|
||||
if (!/^\/items\/[abc]$/.test(action.url)) {state.notice='invalid';break;}
|
||||
request({kind:action.kind,url:action.url,key:null});break;
|
||||
case 'traverse': {
|
||||
if (!Number.isSafeInteger(action.delta)) throw Error('Invalid fixture delta');
|
||||
const index=state.index+action.delta,entry=state.entries[index];
|
||||
if (!entry || index === state.index) {state.notice='boundary';break;}
|
||||
request({kind:'traverse',url:entry.url,key:entry.key});break;
|
||||
}
|
||||
case 'choose': {
|
||||
if (!['save','discard','cancel'].includes(action.choice)) throw Error('Invalid fixture choice');
|
||||
const intent=state.departure;
|
||||
if (!intent || intent.id !== action.id) {state.notice='stale-choice';break;}
|
||||
if (action.choice === 'cancel') {state.departure=null;state.notice='cancelled';break;}
|
||||
if (intent.sourceLifetime !== state.lifetime || intent.sourceRevision !== state.draft?.revision) {state.departure=null;state.notice='stale-departure';break;}
|
||||
if (!readable()) {blocked();break;}
|
||||
if (unresolved()) {state.notice='unresolved';break;}
|
||||
if (action.choice === 'save') startWrite(intent);
|
||||
else {retained.delete(ownerKey());commitNavigation(intent);}
|
||||
break;
|
||||
}
|
||||
default:throw Error('Unknown fixture action');
|
||||
}
|
||||
},
|
||||
snapshot() {return structuredClone({...state,dirty:dirty(),outcomes:readable() ? ledger() : []});},
|
||||
async settle(op) {if (!operations.has(op)) throw Error('Unknown fixture operation');await operations.get(op);}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import fs from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {createWorkflow} from './candidate.mjs';
|
||||
import {controlledAdapter} from './double.mjs';
|
||||
export function frozen() {
|
||||
const bytes=fs.readFileSync(new URL('./expectations.json',import.meta.url));
|
||||
const identity=JSON.parse(fs.readFileSync(new URL('./identity.json',import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256) throw Error('Changed frozen oracle');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export async function compare(factory=createWorkflow,cases=frozen().cases,adapterFactory=controlledAdapter) {
|
||||
const rows=[];
|
||||
for (const scenario of cases) {
|
||||
const adapter=adapterFactory(),model=factory(adapter);
|
||||
for (const [step,entry] of scenario.steps.entries()) {
|
||||
const action=structuredClone(entry.action);
|
||||
if (['complete','ack','fail'].includes(action.type)) {adapter.finish(action.op,action.value,action.type === 'fail');await model.settle(action.op);}
|
||||
else if (action.type === 'commit') adapter.commit(action.op);
|
||||
else if (action.type === 'service') adapter.service(action.scope,action.allowed);
|
||||
else model.dispatch(action);
|
||||
const actual=model.snapshot(),effects=adapter.inspect(),expectedEffects={calls:entry.calls,effects:entry.effects};
|
||||
rows.push({id:scenario.id,step,actual,expected:entry.expected,effects,expectedEffects,
|
||||
passed:isDeepStrictEqual(actual,entry.expected) && isDeepStrictEqual(effects,expectedEffects)});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const rows=await compare();
|
||||
console.log(JSON.stringify({kind:'synthetic-proposed-composition',checkpoints:rows.length,failed:rows.filter(row=>!row.passed)},null,2));
|
||||
if (rows.some(row=>!row.passed)) process.exitCode=1;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Independent volatile service and pre-commit history records; no real I/O/auth.
|
||||
export function controlledAdapter() {
|
||||
const jobs=new Map(),calls=[],effects=[],versions=new Map(),grants=new Map([['a/x',true],['b/x',true]]);
|
||||
const key=row=>JSON.stringify([row.scope,row.url]);
|
||||
function get(op) {const job=jobs.get(op);if (!job) throw Error('Unknown fixture operation');return job;}
|
||||
function request(kind,row) {
|
||||
if (jobs.has(row.op)) throw Error('Duplicate fixture operation');
|
||||
calls.push({kind,...structuredClone(row)});
|
||||
return new Promise((resolve,reject)=>jobs.set(row.op,{kind,...structuredClone(row),resolve,reject,committed:false,responded:false}));
|
||||
}
|
||||
return {
|
||||
read:request.bind(null,'read'),write:request.bind(null,'write'),
|
||||
history(row) {effects.push({kind:'history',...structuredClone(row)});},
|
||||
service(scope,allowed) {grants.set(scope,allowed);},
|
||||
commit(op) {
|
||||
const row=get(op);if (row.kind !== 'write' || row.committed) throw Error('Invalid fixture commit');
|
||||
const version=versions.get(key(row)) ?? 1;row.committed=true;
|
||||
row.receipt=grants.get(row.scope) !== true ? {status:'denied',version:null}
|
||||
: row.base !== version ? {status:'conflict',version} : {status:'saved',version:version+1};
|
||||
if (row.receipt.status === 'saved') {
|
||||
versions.set(key(row),row.receipt.version);
|
||||
effects.push({kind:'write',op,scope:row.scope,url:row.url,text:row.text,version:row.receipt.version});
|
||||
}
|
||||
},
|
||||
finish(op,value,failed=false) {
|
||||
const row=get(op);if (row.responded) throw Error('Already completed fixture operation');
|
||||
if (!failed && row.kind === 'write' && !row.committed) throw Error('Uncommitted fixture acknowledgement');
|
||||
row.responded=true;
|
||||
if (failed) row.reject(Error('Synthetic response loss or read error'));
|
||||
else row.resolve(row.kind === 'read' ? structuredClone(value) : structuredClone(row.receipt));
|
||||
},
|
||||
inspect() {return structuredClone({calls,effects});}
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"kind": "proposed-composition-not-original-behavior",
|
||||
"author": "Same task agent; self-review only",
|
||||
"oracleMethod": "Explicit expected checkpoint patches expanded to full literal state/call/effect JSON before candidate adaptation; no transition simulator or candidate output",
|
||||
"expectationsSha256": "ac0f2402c11d3c9469c80f52ea4d430d163f0da0f5abd6e8230e70835e187c71",
|
||||
"scenarios": 20,
|
||||
"checkpoints": 189,
|
||||
"ordinaryCheckpoints": 15,
|
||||
"amendments": [
|
||||
{
|
||||
"originalSha256": "029f6a28cf4986dbf8421d18c6b69a291158a3405d68f680f02f6ed5b87a5ff1",
|
||||
"reason": "Two login checkpoints omitted immediately readable same-owner outcomes; the existing proposed visibility rule applies on context change, without waiting for read.",
|
||||
"checkpoints": [
|
||||
"expiry-recovery-and-late-commit:7",
|
||||
"committed-response-loss-stays-unresolved:8"
|
||||
],
|
||||
"evidence": "Initial freeze and failed run retained privately; explicit literal correction, no candidate-output capture; candidate unchanged."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Conflicting observations: one bounded sorting example
|
||||
|
||||
This source-exposed authored example extends the existing finding ledger,
|
||||
requirements and dependency/revalidation approach. It is not a new evidence
|
||||
framework. Original examples, oracles and historical observations are unchanged.
|
||||
|
||||
Read [sources](sources.json), [literal expectations](expectations.json) and the
|
||||
[worked decision](decision.json) before [the checker](run.mjs). Their bytes and the
|
||||
[local specimen](sorter.mjs) are frozen in [identity](identity.json). Hashes bind
|
||||
bytes, not truth or independent acceptance. All dates, runtime notes, recording
|
||||
descriptions, documentation and recollections in the inputs are synthetic; no
|
||||
actual video, original app, remote service or new model call was used.
|
||||
|
||||
The supplied runtime note preserves `z,a` for equal ranks; a described recording
|
||||
shows `a,z`. Two later reports derive from that same description. Newest docs
|
||||
claim universal insertion ordering for a different release. The recollection of
|
||||
server sorting supplies no remote trace; offline/no accounts remains binding.
|
||||
These sources differ in applicability, not merely in vote count or source type.
|
||||
|
||||
X1 controls the local artifact, fresh equal-ranked input and process while varying
|
||||
one setting. H-setting predicts `z,a` with insertion and `a,z` with title;
|
||||
H-fixed predicts `z,a` for both. Those are frozen before executing the specimen.
|
||||
Unexpected outputs or missing controls leave X1 inconclusive. Actual successful
|
||||
execution supports H-setting and refutes H-fixed for these cases. It cannot
|
||||
identify the described recording's cause: version, setting, reset and intermediate
|
||||
actions remain unknown. H-capture and remote behavior remain unresolved.
|
||||
|
||||
The new local check supersedes only the universal-insertion inference. Keep the
|
||||
old insertion observation historical and reassess the tie-policy requirement,
|
||||
design decision and order acceptance. Retain unrelated scoped schema evidence;
|
||||
remote parity stays unverified. A passing old insertion test cannot verify both
|
||||
settings. Desired tie policy still needs the focused question in decision.json;
|
||||
the specification request grants no implementation authority. Unaffected schema
|
||||
and primary-rank specification can continue.
|
||||
|
||||
```sh
|
||||
node evals/conflicting-evidence/run.mjs NEW_DIRECTORY
|
||||
node --test tests/conflicting-evidence.test.mjs
|
||||
```
|
||||
|
||||
The run records actual local outputs, Node version and frozen identities in a new
|
||||
directory; existing destinations are refused. Tests reject faulty specimen
|
||||
behavior and misleading structured decisions with specific assertions, including
|
||||
copy votes, source precedence, backend claims, lost unknowns and stale acceptance.
|
||||
Fixed-cell equality does not judge equivalent prose or arbitrary evidence.
|
||||
Manual contextual assessment must still check whether a proposed explanation
|
||||
fits the sources and whether the investigation distinguishes consequential
|
||||
alternatives. Same-author fixture reasoning and local execution do not measure
|
||||
live model understanding, independent acceptance or original-application parity.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mode": "specify-only",
|
||||
"hardRequirements": ["offline", "no-accounts"],
|
||||
"sourceGroups": [["E-R"],["E-V","E-C1","E-C2"],["E-D"],["E-N"]],
|
||||
"applicability": {"local":"frozen sorter.mjs; authored-1; selected input; insertion/title only", "recording":"unknown artifact/version/settings/reset; no causal resolution", "documentation":"authored-2; not selected-target verification", "narrative":"recollection plus explicit desired offline/no-accounts scope"},
|
||||
"hypotheses": ["H-setting", "H-fixed", "H-capture"],
|
||||
"experiment": {"id":"X1", "predictions":{"H-setting":{"insertion":["z","a"],"title":["a","z"]},"H-fixed":{"insertion":["z","a"],"title":["z","a"]}}, "inconclusive":"unexpected output or uncontrolled identity/conditions"},
|
||||
"conclusion": {"classification":"observed local outputs; H-setting supported within X1", "source":"E-X1", "fixedInsertion":"refuted for selected local artifact/settings/input", "recordingCause":"unresolved", "remoteBehavior":"unverified"},
|
||||
"revision": {"historical":["E-R","E-V","E-C1","E-C2","E-D","E-N"], "superseded":["F-local-order: universal insertion inference"], "reassess":["R-tie-policy","D-sort-design","T-order-acceptance"], "retainScoped":["F-schema","T-schema"], "unresolved":["F-remote","T-remote-parity","E-V cause"]},
|
||||
"desiredPolicy": {"status":"unresolved", "question":"For the offline reconstruction, should equal ranks retain insertion or use title?", "reason":"available original evidence does not choose the user's intended tie policy", "unaffectedWork":"retain schema findings and specify unaffected rank ordering", "implementation":"not authorized by this specification request"}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"insertion": ["z", "a"],
|
||||
"title": ["a", "z"],
|
||||
"differentRanks": ["a", "z"],
|
||||
"empty": [],
|
||||
"inputAfter": [{"id":"z","rank":1,"title":"Zulu"},{"id":"a","rank":1,"title":"Alpha"}]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"kind": "authored fixed conflict case; not original-app evidence",
|
||||
"files": {
|
||||
"decision.json": "fe19cb0132a312a7c4427b989aba469f9749d705ad1f0c8203deb5d199176f73",
|
||||
"expectations.json": "e3a59eadee1b5f17c75dca90c5e648d2b029848d22531ee35cceb4052c0889ad",
|
||||
"sorter.mjs": "cfae6cf2ae27afdb127aa7ae44923f177ce7195d65c70e211a489a9b43bf6b0e",
|
||||
"sources.json": "1143b9943d6f5b40f6b7cb1763992d654f6d8f681ab080ddbfb27868290c5aec"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// One fixed worked case, not a general evidence resolver or model evaluator.
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import assert from 'node:assert/strict';
|
||||
import { sortRows } from './sorter.mjs';
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const read = name => JSON.parse(fs.readFileSync(path.join(here, name), 'utf8'));
|
||||
const sha = bytes => createHash('sha256').update(bytes).digest('hex');
|
||||
export function verifyFreeze() {
|
||||
const identity = read('identity.json');
|
||||
assert.deepEqual(Object.keys(identity.files).sort(), ['decision.json','expectations.json','sorter.mjs','sources.json']);
|
||||
for (const [name, digest] of Object.entries(identity.files)) {
|
||||
const file = path.join(here, name), stat = fs.lstatSync(file);
|
||||
assert.ok(stat.isFile() && !stat.isSymbolicLink(), 'Frozen regular file required');
|
||||
assert.equal(sha(fs.readFileSync(file)), digest, `Frozen identity mismatch: ${name}`);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
export const workedDecision = () => read('decision.json');
|
||||
|
||||
// Compares only authored structured cells. Human assessment must examine meaning
|
||||
// and source applicability; equivalent free-form answers are not classified here.
|
||||
export function checkDecision(record) {
|
||||
verifyFreeze();
|
||||
const expected = workedDecision();
|
||||
assert.ok(record && typeof record === 'object' && !Array.isArray(record), 'record shape');
|
||||
assert.deepEqual(Object.keys(record).sort(), Object.keys(expected).sort(), 'record fields');
|
||||
for (const field of Object.keys(expected)) assert.deepEqual(record[field], expected[field], `decision field: ${field}`);
|
||||
return 'fixed-cells-passed';
|
||||
}
|
||||
|
||||
export function runCase(destination, sorter = sortRows, record = workedDecision()) {
|
||||
verifyFreeze(); checkDecision(record);
|
||||
const input = read('sources.json').selectedTarget.data, expected = read('expectations.json');
|
||||
for (const row of input) {
|
||||
assert.deepEqual(Object.keys(row).sort(), ['id','rank','title'], 'T-schema selected row fields');
|
||||
assert.equal(typeof row.id, 'string'); assert.equal(typeof row.rank, 'number'); assert.equal(typeof row.title, 'string');
|
||||
}
|
||||
const outputs = {};
|
||||
for (const setting of ['insertion', 'title']) {
|
||||
outputs[setting] = sorter(input, setting);
|
||||
assert.deepEqual(outputs[setting], expected[setting], `local ${setting} outcome`);
|
||||
assert.deepEqual(input, expected.inputAfter, 'input retained after each run');
|
||||
}
|
||||
assert.deepEqual(sorter([{id:'z',rank:2,title:'Zulu'},{id:'a',rank:1,title:'Alpha'}], 'insertion'), expected.differentRanks, 'rank still primary');
|
||||
assert.deepEqual(sorter([], 'title'), expected.empty, 'empty input');
|
||||
assert.throws(() => sorter(input, 'unknown'), /Unknown tie-break setting/, 'unsupported setting must not silently default');
|
||||
const result = { status:'bounded-case-passed', outputs, node:process.version,
|
||||
fixtureIdentitySha256:sha(fs.readFileSync(path.join(here, 'identity.json'))),
|
||||
sorterSha256:sha(fs.readFileSync(path.join(here, 'sorter.mjs'))),
|
||||
conclusion:record.conclusion, revision:record.revision,
|
||||
contextualAssessment:'manual; author supplied the worked decision',
|
||||
originalApplicationParity:'not tested', liveModelUnderstanding:'not tested' };
|
||||
if (destination) {
|
||||
fs.mkdirSync(destination); // exclusive destination; never overwrite prior evidence
|
||||
fs.writeFileSync(path.join(destination, 'result.json'), JSON.stringify(result, null, 2)+'\n', {flag:'wx'});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
try {
|
||||
const [destination, ...extra] = process.argv.slice(2);
|
||||
assert.ok(destination && !extra.length, 'Usage: node evals/conflicting-evidence/run.mjs NEW_DIRECTORY');
|
||||
console.log(JSON.stringify(runCase(path.resolve(destination)), null, 2));
|
||||
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Authored local specimen, not a recovered application. Equal ranks expose the setting.
|
||||
export function sortRows(rows, tieBreak) {
|
||||
if (!['insertion', 'title'].includes(tieBreak)) throw new Error('Unknown tie-break setting');
|
||||
return [...rows].sort((a, b) => a.rank - b.rank ||
|
||||
(tieBreak === 'title' ? (a.title < b.title ? -1 : a.title > b.title ? 1 : 0) : 0))
|
||||
.map(row => row.id);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"scope": "Synthetic local sorting investigation; specify an offline reconstruction, do not build it",
|
||||
"hardRequirements": ["offline", "no-accounts"],
|
||||
"selectedTarget": {"artifact": "sorter.mjs", "version": "authored-1", "runtime": "Node >=20", "permissions": "local-only, no account", "locale": "explicit code-point title comparison", "data": [{"id":"z","rank":1,"title":"Zulu"},{"id":"a","rank":1,"title":"Alpha"}]},
|
||||
"evidence": [
|
||||
{"id":"E-R", "kind":"runtime-note", "origin":"R1", "sourceDate":"2026-10-17", "captureDate":"2026-10-17", "location":"supplied run, output line 1", "version":"authored-1", "artifact":"sorter.mjs", "environment":"Node >=20, local-only", "setting":"insertion", "input":"selectedTarget.data, fresh array", "output":["z","a"], "limit":"supplied authored note; rerun below supplies current execution evidence"},
|
||||
{"id":"E-V", "kind":"recording-description", "origin":"V1", "sourceDate":"2026-10-18", "captureDate":"unknown", "location":"described frames 1 and 3", "version":"unknown", "artifact":"unknown", "environment":"unknown", "setting":"unknown", "input":"rows appear equal-ranked; intermediate actions/reset missing", "output":["a","z"], "limit":"authored description only; no actual video or backend trace"},
|
||||
{"id":"E-C1", "kind":"summary", "origin":"V1", "derivedFrom":"E-V", "sourceDate":"2026-10-19", "claim":"equal ranks reordered"},
|
||||
{"id":"E-C2", "kind":"report", "origin":"V1", "derivedFrom":"E-C1", "sourceDate":"2026-10-20", "claim":"equal ranks reordered"},
|
||||
{"id":"E-D", "kind":"documentation", "origin":"D1", "sourceDate":"2026-10-20", "captureDate":"not applicable", "location":"authored release-2 note, paragraph 1", "version":"authored-2", "artifact":"unavailable", "claim":"equal ranks always preserve insertion order", "limit":"different release; no selected-target execution"},
|
||||
{"id":"E-N", "kind":"user-description", "origin":"N1", "sourceDate":"2026-10-20", "location":"authored recollection", "claim":"I remember server sorting; I need an offline reconstruction without accounts", "limit":"recollection does not establish a remote effect or authorize implementation"}
|
||||
],
|
||||
"hypotheses": [
|
||||
{"id":"H-setting", "claim":"same local artifact can produce both orders when tie-break changes", "predicted":{"insertion":["z","a"],"title":["a","z"]}},
|
||||
{"id":"H-fixed", "claim":"selected artifact always preserves insertion regardless of setting", "predicted":{"insertion":["z","a"],"title":["z","a"]}},
|
||||
{"id":"H-capture", "claim":"missing version/settings/actions can account for the described recording", "predicted":"either local outcome is compatible; local check cannot identify V1 cause"}
|
||||
],
|
||||
"check": {"id":"X1", "authorization":"bounded local authored fixture execution only", "controls":["same frozen artifact", "same fresh input", "same process/runtime", "vary only tieBreak", "no IO or remote calls"], "inconclusive":"different outputs than either prediction, missing identity, or uncontrolled conditions require investigation"},
|
||||
"historicalFindings": [
|
||||
{"id":"F-local-order", "classification":"inference", "source":"E-R", "claim":"all settings preserve insertion", "limit":"overgeneralization from the insertion case; old T-order-acceptance covered insertion only"},
|
||||
{"id":"F-schema", "classification":"static observation", "source":"selectedTarget.data", "claim":"these two fixture rows have id:string, rank:number, title:string", "test":"T-schema checks only these selected rows, not a universal format"},
|
||||
{"id":"F-remote", "classification":"unknown", "source":"E-N", "claim":"no remote effect verified", "test":"T-remote-parity unrun"}
|
||||
],
|
||||
"dependencies": {"F-local-order":["R-tie-policy","D-sort-design","T-order-acceptance"], "F-schema":["T-schema"], "F-remote":["T-remote-parity"]},
|
||||
"laterEvidence": {"id":"E-X1", "kind":"actual run of X1", "appliesTo":"current frozen sorter.mjs, selected data, two named settings only", "supersedes":"F-local-order: universal insertion inference", "doesNotSupersede":["E-R historical insertion observation", "E-V unknown recording cause", "F-schema unrelated evidence", "F-remote unresolved"]}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# Core instruction and reference-loading review
|
||||
|
||||
Same-author source-exposed manual semantic assessment; physically read and strict UTF8 decode the listed entire files in both snapshots once per row. Author-selected read sets, not observed model/host loading. Base REA and selected target/checkpoint evidence excluded as unchanged inputs. Conditional dependency links assessed by applicability, not transitively preloaded. Full-file totals are conservative for these selected sets, not measurements of partial section loading.
|
||||
|
||||
No extra model/agent/provider/cost. Not a classifier, token/usage estimate, instruction-following score, cross-model or host-activation validation. Existing executable scenarios test mechanisms rather than these prose interpretations. Mandatory unrelated boundaries remain in core.
|
||||
|
||||
Baseline: `d530eb5e10948d9f1701101c306564fc87a834dd` (v1.3.55). Core UTF8 bytes: **34,569 before; 29,466 after**. These are actual byte counts, not tokens. Universal activation/update, consent, cost, evidence trust, partial capability, recovery/applicability and honest-completion obligations remain in the core. Conditional routes are mandatory when applicable; their prior summaries now open the selected references.
|
||||
|
||||
The finite cases below preserve corrections, exclusions and overlapping concerns. Each read set includes `SKILL.md` unless companion activation is excluded. Names denote `references/<name>.md`; necessary target/checkpoint records and base REA remain selected independently. No whole-catalogue preload is assumed in either version.
|
||||
|
||||
| ID and request | Manual decision and selected references | Before bytes | After bytes | Delta |
|
||||
| --- | --- | ---: | ---: | ---: |
|
||||
| N0: Explain this DLL export; no reconstruction goal. | Base REA only; companion excluded. No blueprint or implementation. Reads: (none). | 0 | 0 | +0 |
|
||||
| N1: I may rebuild it someday; explain only this decoder first. | Narrow explanation/contract; aspiration is not build authority. No whole-product blueprint. Reads: `reconstruction-methods`. | 52,260 | 47,157 | -5,103 |
|
||||
| S1: Map the main workflows so I can plan a replacement; no code yet. | Specification only: actionable requirements/design/sequence and explicit gaps. Reads: `blueprint`, `reconstruction-methods`. | 72,985 | 67,882 | -5,103 |
|
||||
| B1: Make just the selected decoder work here from that contract. | Authorized narrow build; independent expected cases and untrusted-input boundary, no added UI/service. Reads: `implementation`, `reconstruction-methods`, `reconstruction-security`. | 74,092 | 69,378 | -4,714 |
|
||||
| C1: Change my existing reconstruction to retain item identity; keep my other edits. | Inspect custom work and retained contracts; implement only the selected change and regressions. Reads: `safe-changes`, `implementation`, `checkpoints`, `reconstruction-methods`, `search-filter-sort`. | 95,971 | 91,530 | -4,441 |
|
||||
| R1: Continue the spec, but the selected target is version 2 now. | Retain specification mode; mark dependent claims stale, preserve applicable evidence. No implementation. Reads: `checkpoints`, `version-changes`, `reconstruction-methods`. | 74,062 | 69,209 | -4,853 |
|
||||
| R2: Pick up those notes; the old target and report bytes are missing. | Missing identity/applicability stays unknown; obtain smallest missing evidence, do not fabricate match. Reads: `checkpoints`, `reconstruction-methods`. | 70,637 | 65,534 | -5,103 |
|
||||
| E1: Explain retry duplication from this client trace; I cannot access the server. | Keep hypotheses bounded and real-service requirements pending; no invented backend, credentials or provider contact. Reads: `unavailable-backends`, `reconstruction-methods`, `reconstruction-security`. | 65,183 | 60,776 | -4,407 |
|
||||
| E2: Plan this resizable form from screenshots; no live app or code yet. | Visible/frame/crop/timing limits; proposed unseen behavior explicit; responsive, accessibility and validation boundaries overlap. Reads: `visual-evidence`, `blueprint`, `interface-accessibility`, `form-validation`, `reconstruction-methods`. | 97,468 | 94,119 | -3,349 |
|
||||
| E3: The later trace disagrees with the saved explanation; work out what still applies. | Compare source applicability, retain competing explanations and prior results; newer date is not automatic supersession. Reads: `reconstruction-methods`, `checkpoints`. | 70,637 | 65,534 | -5,103 |
|
||||
| F1: REA failed halfway through this selected investigation; continue useful work. | Preserve findings and failed operation, report partial capability; no invented success or automatic recursive retry. Reads: `checkpoints`, `reconstruction-methods`. | 70,637 | 65,534 | -5,103 |
|
||||
| U1: Check optional weekly updates and explain this integration warning; do not apply anything. | One local on-use check, no recursive check; preserve consent and warning state. Online no-charge gate and exact-offer approval still required. Reads: `updates`. | 50,677 | 45,574 | -5,103 |
|
||||
| O1: Finish the authorized draft recovery flow when account expiry and late saves overlap. | Read overlapping state/identity/effect contracts; distinguish saved/checkpoint/editor state, nonmutating comparison, scoped copy and effect-time revalidation. Preserve DR-NAR-001/DC-A001. Reads: `blueprint`, `implementation`, `reconstruction-methods`, `draft-recovery`, `unsaved-navigation`, `session-permissions`, `asynchronous-results`, `reconstruction-security`. | 118,418 | 114,126 | -4,292 |
|
||||
| X1: Actually only explain the rounding setting and reset behavior; no code now. | Current correction overrides prior build; numeric representation, rounding stage and preference presence/source/reset remain distinct. Reads: `numeric-contracts`, `settings-preferences`, `reconstruction-methods`. | 64,019 | 59,653 | -4,366 |
|
||||
| P1: Reimplement only decodeFrame faster with identical outputs. | Authorized scoped build/optimization; correctness and comparable baseline before measured benefit; focused methods only when consequential. Reads: `implementation`, `performance`, `reconstruction-methods`, `validation-methods`. | 81,677 | 76,574 | -5,103 |
|
||||
| K1: Build the selected encrypted file importer; reject bad inputs without changing saved data. | Recover explicit profile and independent rejection/interoperability cases; known algorithm does not supply keys; preserve prior bytes and limit durability claims. Reads: `implementation`, `file-compatibility`, `cryptographic-compatibility`, `reconstruction-security`, `reconstruction-methods`. | 82,742 | 78,672 | -4,070 |
|
||||
|
||||
The reduction depends on which references apply. Moving detail into selected references increases those reference sizes; a broad task that eventually loads many references can erase the core reduction. This table does not establish lower billed usage, faster execution or better decisions.
|
||||
|
||||
Retained mechanism checks include the existing function/workflow oracles and deliberately wrong candidates, reference walkthrough/reconstruction, resume/selective/environment/recovery, conflicts, unavailable backend, visual evidence, draft recovery and draft-conflict suites. Their actual run results belong in the exact proposal/release evidence. Passing those tests does not prove these natural-language interpretations or independent review acceptance.
|
||||
|
||||
The earlier [first-use judgments](first-use.md) remain historical source-exposed manual evidence; they are not replaced by or counted as fresh model evaluations here.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Frozen expectations and provenance
|
||||
|
||||
The literal `oracle.json` was reasoned before the candidate implementation on
|
||||
21 October 2026. It specifies a synthetic profile, not an observed application.
|
||||
Author and checker are source-exposed and the same author; final non-author
|
||||
review is separate. The candidate never supplies expected values. SHA-256 is
|
||||
recorded in `identity.json`; it detects drift, not correctness or independence.
|
||||
|
||||
- Date meaning is an explicit proposed requirement: Gregorian `YYYY-MM-DD`
|
||||
stays that day for every supported viewer zone. Fixed two-digit day/month
|
||||
display uses D/M/Y for en-GB and M/D/Y for en-US. Thus March 4 is `04/03/2026`
|
||||
or `03/04/2026`. This is a deliberately bounded numeric display contract, not
|
||||
a claim about all CLDR punctuation, digits or locales. 2024 is a leap year;
|
||||
2026 is not. March 31 precedes April 1 regardless of display lexicography.
|
||||
- [IANA tzdb 2026b Europe](https://data.iana.org/time-zones/tzdb-2026b/europe)
|
||||
supplies EU rules: summer time starts on March's last Sunday at 01:00 UTC,
|
||||
ends on October's last Sunday at 01:00 UTC. The Norway commentary selects
|
||||
Europe/Berlin for post-1970 rules; the
|
||||
[backward links](https://data.iana.org/time-zones/tzdb-2026b/backward)
|
||||
link Europe/Oslo to Europe/Berlin. Its standard offset is +01:00; summer
|
||||
adds one hour. March 29 and October 25 are the last Sundays in 2026 (the
|
||||
next Sundays, April 5 and November 1, fall in the next month).
|
||||
- Add 60 minutes in winter or 120 in summer to UTC. January 15 22:59:59Z is
|
||||
23:59:59; 23:00:00Z is January 16 00:00:00. March 29 00:59:59Z is 01:59:59
|
||||
at +01; one second later is 03:00:00 at +02. October 25 00:59:59Z is
|
||||
02:59:59 at +02; one second later is 02:00:00 at +01. These arithmetic
|
||||
expectations were not produced by Intl or the candidate.
|
||||
- Wall 02:30 in October has two occurrences: subtract two hours for 00:30Z
|
||||
or one for 01:30Z. Require an explicit consistent offset. March's 02:30
|
||||
exists under neither offset: +01 maps to 01:30Z, already 03:30 locally;
|
||||
+02 maps to 00:30Z, still 01:30 locally. Reject both, all unqualified wall
|
||||
inputs, and a summer +01 offset. This is the proposed policy, not a
|
||||
universal library default or recovered original policy.
|
||||
- Persistence retains the calendar date, canonical seconds-only Z instant,
|
||||
original qualified wall input and named zone. The literal JSON includes
|
||||
fixed field order and LF. Reopen with another locale/UTC display preserves
|
||||
those bytes and values. Rejected invalid-day input preserves prior bytes.
|
||||
|
||||
The selected runtime consumes its own installed zone data; it does not load
|
||||
the cited tzdb file. Report Node/V8/ICU/CLDR/tz metadata and explicit resolved
|
||||
Intl options. Agreement on these 2026 vectors is not proof of all data matching
|
||||
2026b or future legislation. No dependency, host-zone mutation or remote
|
||||
execution is needed. No leap seconds, other calendars, recurrence, arbitrary
|
||||
zones/locales, crash durability or concurrent-writer guarantees are covered.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Bounded offline date/locale example
|
||||
|
||||
This proposed synthetic contract exercises the portable [date guidance](../../skills/rea-reconstruction-companion/references/date-locale.md).
|
||||
It does not recover another app's intent. No REA binary/runtime investigation was
|
||||
needed for this source-exposed example. Same-author implementation/checks are not
|
||||
independent review or model/host certification. Use existing Node 20+; no packages,
|
||||
network or host-zone changes are needed:
|
||||
|
||||
```sh
|
||||
node evals/date-locale/check.mjs
|
||||
node --test tests/date-locale.test.mjs
|
||||
```
|
||||
|
||||
Read [frozen expectations and provenance](ORACLE.md), `oracle.json` and its
|
||||
`identity.json` before `candidate.mjs`. The checker compares actual conversion
|
||||
outputs with separately reasoned literal values. It records Node/V8/ICU/CLDR/tz
|
||||
and resolved Intl options. The cited tzdb is a reasoning source, not a dependency
|
||||
loaded by the test. A mismatch must be investigated; do not regenerate the oracle
|
||||
from the runtime output. Hash binding is drift detection only.
|
||||
|
||||
| Requirement | Component and selected acceptance |
|
||||
| --- | --- |
|
||||
| D1: date-only stays a date | `calendar`/`date`: Gregorian years 2000–2099, strict YYYY-MM-DD. March 4 remains March 4 for the west-coast viewer; en-GB/en-US presentation order differs. Invalid and localized date input rejects. |
|
||||
| D2: instant projection uses selected rules | `project`: strict seconds-only Z input; explicit Gregorian/Latin/h23 Intl options. Winter midnight and spring/autumn instants straddle the named Oslo rule changes. |
|
||||
| D3: wall time needs an offset consistent with its zone | `wall`: strict seconds plus numeric offset, checked by projecting the resulting instant. Both October occurrences work with their matching offsets; both March gap offsets and unqualified inputs reject. |
|
||||
| D4: display does not become canonical data | `sort`/`encode`/`decode`: date sorting crosses March/April; literal canonical JSON retains date, instant, original wall offset and named zone. |
|
||||
| D5: rejected save preserves prior data | `save`/`reopen`: real disposable file writes/reads; changed locale/UTC display leaves bytes and values unchanged; invalid or inconsistent records reject before write. |
|
||||
|
||||
Projection accepts only Europe/Oslo, America/Los_Angeles and UTC; locale display
|
||||
accepts only fixed two-digit en-GB D/M/Y and en-US M/D/Y. Date-only formatting uses
|
||||
components directly, with no artificial instant. This display profile does not
|
||||
claim to reproduce arbitrary Intl punctuation or localized month names. Inputs
|
||||
are synthetic; there are no current-time reads. Range/precision/locale limits
|
||||
are deliberate requirements rather than recovered universal semantics.
|
||||
|
||||
Four temporary faulty candidates each pass all four ordinary cases. The selected
|
||||
boundary tests then reject their actual values: date-through-UTC shifts the west
|
||||
viewer date; fixed +01 misses the spring rule; formatted-string sorting reverses
|
||||
March 31/April 1; unchecked offset conversion accepts a nonexistent wall time.
|
||||
The tests retain diagnostics with the ordinary counts and distinguishing actual
|
||||
outputs. A parse/load error or timeout is not an intended mutation kill.
|
||||
|
||||
Persistence uses a simple file write after validation: no process-crash, power-loss,
|
||||
concurrency or hostile-path safety is claimed. No general wall-time resolver,
|
||||
recurrence/scheduling, full calendar/localization system, leap seconds or custom
|
||||
zone database is provided. Selected installed-data results are finite evidence,
|
||||
not a promise about future rules, every zone or another runtime. Historical
|
||||
fixtures and other examples remain unchanged. This directory is repository-only;
|
||||
installed skill references must remain self-contained.
|
||||
@@ -0,0 +1,77 @@
|
||||
// Bounded teaching profile. No Date.parse of domain inputs or host-default zone.
|
||||
import fs from 'node:fs';
|
||||
const zones = ['Europe/Oslo', 'America/Los_Angeles', 'UTC'];
|
||||
const fail = code => { throw new RangeError(code); };
|
||||
const zoneCheck = zone => { if (!zones.includes(zone)) fail('zone'); };
|
||||
export function calendar(value) {
|
||||
if (typeof value !== 'string' || !/^20\d{2}-\d{2}-\d{2}$/.test(value)) fail('date');
|
||||
const [y, m, d] = value.split('-').map(Number);
|
||||
const days = [31, y % 4 === 0 ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
if (m < 1 || m > 12 || d < 1 || d > days[m - 1]) fail('date');
|
||||
return value;
|
||||
}
|
||||
function clock(value, code) {
|
||||
if (typeof value !== 'string' || !/^20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(value)) fail(code);
|
||||
calendar(value.slice(0, 10));
|
||||
const [h, m, s] = value.slice(11).split(':').map(Number);
|
||||
if (h > 23 || m > 59 || s > 59) fail(code);
|
||||
return Date.UTC(...[...value.slice(0, 10).split('-').map(Number), h, m, s].map((n, i) => i === 1 ? n - 1 : n));
|
||||
}
|
||||
export function instant(value) {
|
||||
if (typeof value !== 'string' || !value.endsWith('Z')) fail('instant');
|
||||
return clock(value.slice(0, -1), 'instant');
|
||||
}
|
||||
export const formatOptions = zone => ({timeZone: zone, calendar: 'gregory', numberingSystem: 'latn', hourCycle: 'h23',
|
||||
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'});
|
||||
export function project(value, zone) {
|
||||
zoneCheck(zone);
|
||||
const ms = instant(value);
|
||||
const parts = Object.fromEntries(new Intl.DateTimeFormat('en-GB', formatOptions(zone)).formatToParts(ms)
|
||||
.filter(p => p.type !== 'literal').map(p => [p.type, p.value]));
|
||||
const date = `${parts.year}-${parts.month}-${parts.day}`;
|
||||
const wall = `${date}T${parts.hour}:${parts.minute}:${parts.second}`;
|
||||
return {date, wall, offsetMinutes: (clock(wall, 'wall') - ms) / 60000};
|
||||
}
|
||||
export function date(value, locale, viewerZone) {
|
||||
calendar(value); zoneCheck(viewerZone);
|
||||
if (!['en-GB', 'en-US'].includes(locale)) fail('locale');
|
||||
const [y, m, d] = value.split('-');
|
||||
return {value, display: locale === 'en-GB' ? `${d}/${m}/${y}` : `${m}/${d}/${y}`};
|
||||
}
|
||||
export function wall(value, zone) {
|
||||
zoneCheck(zone);
|
||||
if (typeof value !== 'string' || !/^20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/.test(value)) fail('wall');
|
||||
const local = value.slice(0, 19), hours = Number(value.slice(20, 22)), minutes = Number(value.slice(23));
|
||||
if (hours > 14 || minutes > 59 || (hours === 14 && minutes !== 0) || value.endsWith('-00:00')) fail('wall');
|
||||
const offset = (hours * 60 + minutes) * (value[19] === '+' ? 1 : -1);
|
||||
const result = new Date(clock(local, 'wall') - offset * 60000).toISOString().replace('.000Z', 'Z');
|
||||
const projected = project(result, zone);
|
||||
if (projected.wall !== local || projected.offsetMinutes !== offset) fail('wall-offset');
|
||||
return {instant: result, zone, sourceWall: value};
|
||||
}
|
||||
export function sort(values) {
|
||||
if (!Array.isArray(values)) fail('date');
|
||||
return values.map(calendar).sort();
|
||||
}
|
||||
export function encode(record) {
|
||||
if (!record || Array.isArray(record) || typeof record !== 'object' || record.schema !== 1 ||
|
||||
Object.keys(record).sort().join(',') !== 'calendarDate,instant,schema,sourceWall,zone') fail('record');
|
||||
calendar(record.calendarDate); instant(record.instant);
|
||||
if (wall(record.sourceWall, record.zone).instant !== record.instant) fail('record');
|
||||
return JSON.stringify({schema: 1, calendarDate: record.calendarDate, instant: record.instant,
|
||||
zone: record.zone, sourceWall: record.sourceWall}) + '\n';
|
||||
}
|
||||
export function decode(bytes) {
|
||||
const record = JSON.parse(bytes);
|
||||
encode(record);
|
||||
return record;
|
||||
}
|
||||
export function save(file, record) {
|
||||
const bytes = encode(record); // All semantic rejection precedes the write.
|
||||
fs.writeFileSync(file, bytes, 'utf8'); // No crash/concurrency/hostile-path guarantee.
|
||||
}
|
||||
export function reopen(file, locale, viewerZone) {
|
||||
const record = decode(fs.readFileSync(file, 'utf8'));
|
||||
return {record, calendar: date(record.calendarDate, locale, viewerZone), event: project(record.instant, viewerZone)};
|
||||
}
|
||||
export const operations = {date, project, wall, sort};
|
||||
@@ -0,0 +1,37 @@
|
||||
import fs from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import crypto from 'node:crypto';
|
||||
import {pathToFileURL} from 'node:url';
|
||||
import {operations, formatOptions} from './candidate.mjs';
|
||||
const root = new URL('./', import.meta.url);
|
||||
export function frozen() {
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('identity.json', root)));
|
||||
for (const [name, hash] of Object.entries(identity.files))
|
||||
assert.equal(crypto.createHash('sha256').update(fs.readFileSync(new URL(name, root))).digest('hex'), hash, name);
|
||||
return JSON.parse(fs.readFileSync(new URL('oracle.json', root)));
|
||||
}
|
||||
export function compare(api = operations, cases = [...frozen().ordinary, ...frozen().boundaries]) {
|
||||
return cases.map(row => {
|
||||
let actual;
|
||||
try { actual = api[row.op](...row.args); }
|
||||
catch (error) {
|
||||
if (!(error instanceof RangeError) || !['date','instant','wall','wall-offset','locale','zone','record'].includes(error.message)) throw error;
|
||||
actual = {error: error.message};
|
||||
}
|
||||
let passed = true;
|
||||
try { assert.deepEqual(actual, row.expected); } catch (error) { if (error.code !== 'ERR_ASSERTION') throw error; passed = false; }
|
||||
return {id: row.id, actual, expected: row.expected, passed};
|
||||
});
|
||||
}
|
||||
export function environment() {
|
||||
return {node: process.version, v8: process.versions.v8, icu: process.versions.icu ?? null,
|
||||
cldr: process.versions.cldr ?? null, tz: process.versions.tz ?? null,
|
||||
clock: 'literal inputs; no current-time reads',
|
||||
projection: ['Europe/Oslo','America/Los_Angeles','UTC'].map(zone => new Intl.DateTimeFormat('en-GB', formatOptions(zone)).resolvedOptions()),
|
||||
dateDisplay: 'Explicit en-GB D/M/Y and en-US M/D/Y numeric profile; no locale-string parser'};
|
||||
}
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
const results = compare();
|
||||
console.log(JSON.stringify({profile: frozen().profile, environment: environment(), results, humanReview: 'pending'}, null, 2));
|
||||
if (results.some(r => !r.passed)) process.exitCode = 1;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profile": "synthetic-gregorian-date-oslo-v1",
|
||||
"frozenBeforeCandidate": true,
|
||||
"files": {
|
||||
"oracle.json": "3375c44091594806a57d7002d49588db0e747cb9e7b205287b467839ebe7879a",
|
||||
"ORACLE.md": "c9b91999b846681717666ed6f95507ed10703a2c3ee6747938b764fdd4dd5a18"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"profile": "synthetic-gregorian-date-oslo-v1",
|
||||
"ordinary": [
|
||||
{"id":"date-ordinary","op":"date","args":["2026-03-04","en-GB","Europe/Oslo"],"expected":{"value":"2026-03-04","display":"04/03/2026"}},
|
||||
{"id":"instant-winter","op":"project","args":["2026-01-15T11:00:00Z","Europe/Oslo"],"expected":{"date":"2026-01-15","wall":"2026-01-15T12:00:00","offsetMinutes":60}},
|
||||
{"id":"wall-winter","op":"wall","args":["2026-01-15T12:00:00+01:00","Europe/Oslo"],"expected":{"instant":"2026-01-15T11:00:00Z","zone":"Europe/Oslo","sourceWall":"2026-01-15T12:00:00+01:00"}},
|
||||
{"id":"sort-ordinary","op":"sort","args":[["2026-03-05","2026-03-04"]],"expected":["2026-03-04","2026-03-05"]}
|
||||
],
|
||||
"boundaries": [
|
||||
{"id":"date-west-viewer","op":"date","args":["2026-03-04","en-US","America/Los_Angeles"],"expected":{"value":"2026-03-04","display":"03/04/2026"}},
|
||||
{"id":"date-other-order","op":"date","args":["2026-03-04","en-US","Europe/Oslo"],"expected":{"value":"2026-03-04","display":"03/04/2026"}},
|
||||
{"id":"before-midnight","op":"project","args":["2026-01-15T22:59:59Z","Europe/Oslo"],"expected":{"date":"2026-01-15","wall":"2026-01-15T23:59:59","offsetMinutes":60}},
|
||||
{"id":"at-midnight","op":"project","args":["2026-01-15T23:00:00Z","Europe/Oslo"],"expected":{"date":"2026-01-16","wall":"2026-01-16T00:00:00","offsetMinutes":60}},
|
||||
{"id":"spring-before","op":"project","args":["2026-03-29T00:59:59Z","Europe/Oslo"],"expected":{"date":"2026-03-29","wall":"2026-03-29T01:59:59","offsetMinutes":60}},
|
||||
{"id":"spring-at","op":"project","args":["2026-03-29T01:00:00Z","Europe/Oslo"],"expected":{"date":"2026-03-29","wall":"2026-03-29T03:00:00","offsetMinutes":120}},
|
||||
{"id":"autumn-before","op":"project","args":["2026-10-25T00:59:59Z","Europe/Oslo"],"expected":{"date":"2026-10-25","wall":"2026-10-25T02:59:59","offsetMinutes":120}},
|
||||
{"id":"autumn-at","op":"project","args":["2026-10-25T01:00:00Z","Europe/Oslo"],"expected":{"date":"2026-10-25","wall":"2026-10-25T02:00:00","offsetMinutes":60}},
|
||||
{"id":"fold-earlier","op":"wall","args":["2026-10-25T02:30:00+02:00","Europe/Oslo"],"expected":{"instant":"2026-10-25T00:30:00Z","zone":"Europe/Oslo","sourceWall":"2026-10-25T02:30:00+02:00"}},
|
||||
{"id":"fold-later","op":"wall","args":["2026-10-25T02:30:00+01:00","Europe/Oslo"],"expected":{"instant":"2026-10-25T01:30:00Z","zone":"Europe/Oslo","sourceWall":"2026-10-25T02:30:00+01:00"}},
|
||||
{"id":"gap-standard-offset","op":"wall","args":["2026-03-29T02:30:00+01:00","Europe/Oslo"],"expected":{"error":"wall-offset"}},
|
||||
{"id":"gap-summer-offset","op":"wall","args":["2026-03-29T02:30:00+02:00","Europe/Oslo"],"expected":{"error":"wall-offset"}},
|
||||
{"id":"unqualified-fold","op":"wall","args":["2026-10-25T02:30:00","Europe/Oslo"],"expected":{"error":"wall"}},
|
||||
{"id":"summer-wrong-offset","op":"wall","args":["2026-07-15T12:00:00+01:00","Europe/Oslo"],"expected":{"error":"wall-offset"}},
|
||||
{"id":"sort-month-crossing","op":"sort","args":[["2026-04-01","2026-03-31"]],"expected":["2026-03-31","2026-04-01"]},
|
||||
{"id":"valid-leap-day","op":"date","args":["2024-02-29","en-GB","UTC"],"expected":{"value":"2024-02-29","display":"29/02/2024"}},
|
||||
{"id":"invalid-leap-day","op":"date","args":["2026-02-29","en-GB","UTC"],"expected":{"error":"date"}},
|
||||
{"id":"ambiguous-localized-input","op":"date","args":["03/04/2026","en-GB","UTC"],"expected":{"error":"date"}},
|
||||
{"id":"unqualified-instant","op":"project","args":["2026-03-04T12:00:00","Europe/Oslo"],"expected":{"error":"instant"}},
|
||||
{"id":"unsupported-zone","op":"project","args":["2026-01-15T11:00:00Z","Unknown/Zone"],"expected":{"error":"zone"}},
|
||||
{"id":"unsupported-locale","op":"date","args":["2026-03-04","xx","UTC"],"expected":{"error":"locale"}}
|
||||
],
|
||||
"persistence": {
|
||||
"record":{"schema":1,"calendarDate":"2026-03-04","instant":"2026-10-25T01:30:00Z","zone":"Europe/Oslo","sourceWall":"2026-10-25T02:30:00+01:00"},
|
||||
"export":"{\"schema\":1,\"calendarDate\":\"2026-03-04\",\"instant\":\"2026-10-25T01:30:00Z\",\"zone\":\"Europe/Oslo\",\"sourceWall\":\"2026-10-25T02:30:00+01:00\"}\n",
|
||||
"gbDate":"04/03/2026",
|
||||
"usDate":"03/04/2026",
|
||||
"utcEvent":{"date":"2026-10-25","wall":"2026-10-25T01:30:00","offsetMinutes":0},
|
||||
"rejectedRecord":{"schema":1,"calendarDate":"2026-02-29","instant":"2026-10-25T01:30:00Z","zone":"Europe/Oslo","sourceWall":"2026-10-25T02:30:00+01:00"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
# Browser reload and draft recovery
|
||||
|
||||
This repository-only, dependency-free lab proposes a bounded persistence policy.
|
||||
It does not modify the historical browser-workflow or Pocket Board examples.
|
||||
Run `node evals/draft-recovery/server.mjs` with existing Node >=20, open its printed
|
||||
loopback origin in task-owned disposable tabs, click **Put seed in setup field**
|
||||
then **Apply synthetic envelope**. Stop only that server and close only those tabs.
|
||||
No installation, network API, model call, real account or credential is involved.
|
||||
Standalone skill installations omit the lab and tests.
|
||||
|
||||
## Proposed policy
|
||||
|
||||
Four synthetic saved records are keyed by account A/B and document X/Y. Eight
|
||||
possible draft slots additionally include simulated lifetime 1/2. One versioned
|
||||
localStorage envelope is limited to 64 KiB UTF-8, 2048 UTF-16 units per text and
|
||||
integer revisions 1 through 1,000,000. Unknown/malformed structures fail closed;
|
||||
startup never repairs or replaces them. Setup is a deliberately privileged fixture
|
||||
control that can replace this envelope, including with invalid synthetic bytes.
|
||||
|
||||
Edits checkpoint drafts under an exclusive origin Web Lock. All cooperative
|
||||
writers re-read/validate inside that lock and compare the selected draft snapshot.
|
||||
A second editor cannot silently replace another editor's checkpoint. Missing or
|
||||
refused locks block writes. Checkpoint failure retains memory but reports that the
|
||||
edit lacks reload recovery. No unload/pagehide callback attempts an asynchronous
|
||||
save. A checkpoint must have completed before the observed reload/close boundary.
|
||||
|
||||
Startup reads the saved record and offers matching recovery separately. **Restore
|
||||
offered draft** is explicit and remains unsaved. Save compares the captured base
|
||||
revision and exact checkpoint at the write effect; stale recovery cannot overwrite
|
||||
a newer record. Another tab may resolve/save the shared slot first, so a conflict
|
||||
can leave the old tab's text only in memory. There is no automatic merge or retry.
|
||||
The editor remains editable during saving. If a newer edit arrives after the native
|
||||
write but before acknowledgement, the receipt reconciles the confirmed saved base
|
||||
and checkpoints the newer text through the same slot comparison. It keeps that text
|
||||
unsaved; another writer's checkpoint still blocks replacement. A failed new checkpoint
|
||||
reports memory-only recovery rather than certifying the newer text as saved.
|
||||
**Read latest** explicitly replaces this editor's memory with saved content and a
|
||||
fresh offer; checkpointed drafts remain separate. **Discard selected draft** removes
|
||||
only the unchanged selected slot and reads the saved record.
|
||||
|
||||
Draft eligibility is at most 24 hours with no future timestamp. Stale/future slots
|
||||
remain quarantined until explicit discard; there is no timed purge. Changing
|
||||
account/document/lifetime selects another slot; expiry hides editor/recovery and
|
||||
invalidates undispatched local work. Reload defaults to the visible synthetic A/X/1
|
||||
context. These controls implement no real authentication, authorization, encryption,
|
||||
secure erasure or confidentiality from scripts/users sharing the origin. Retention
|
||||
and clock trust are proposed choices, not recovered original behavior.
|
||||
|
||||
## Distinguishing procedures
|
||||
|
||||
### Explicit conflicting-draft recovery (29 October 2026 proposal)
|
||||
|
||||
**Compare current editor** selects dirty live text; **Compare offered checkpoint**
|
||||
selects the unchanged eligible checkpoint offered by the last context read. Both
|
||||
read the latest saved record without replacing editor text, saved record or draft.
|
||||
The comparison shows exact text/base/latest revision, scope, source/read time and
|
||||
whether editor text is checkpointed or only in memory. It is a last-known snapshot.
|
||||
|
||||
**Confirm keep both as separate copy** explicitly appends the selected text to an
|
||||
optional validated `copies` map in the same schema1 envelope. Legacy envelopes
|
||||
without this field remain readable and are not changed on read. Earlier lab
|
||||
versions reject extended envelopes without repair; this is a proposed local
|
||||
format extension, not a production migration or downgrade guarantee. IDs1..8
|
||||
are allocated from the highest existing ID under the same exclusive Web Lock.
|
||||
At most eight copies total are retained; full capacity fails without eviction,
|
||||
replacement or ID reuse. Copies are scoped to the same account/document/lifetime,
|
||||
2048-unit text and 64KiB total UTF-8 limits. Same-scope copies can be inspected
|
||||
after explicit read latest/reload; the editor remains separate. Eligibility is
|
||||
24hours with no future timestamp, then quarantine without automatic deletion.
|
||||
This example adds no copy editing, deletion, automatic merge or retention purge.
|
||||
|
||||
Confirmation consumes one decision before dispatch and rechecks input sequence,
|
||||
context, clock, saved-record snapshot and selected draft slot at the locked effect.
|
||||
Retained checkpoints also retain their original expiry. Input/restore/context
|
||||
changes and observed storage events invalidate pending choices. Events are an
|
||||
early UI signal; effect checks still apply without them. A peer's newer original
|
||||
or draft requires a fresh comparison. Other-copy additions allocate a fresh ID
|
||||
under the lock; an observed event may conservatively require re-comparison.
|
||||
Repeated confirmation cannot replay a consumed decision. Unknown acknowledgement
|
||||
blocks further decisions until explicit readback/reopen; no automatic retry occurs.
|
||||
Copy receipts never mark editor text saved or replace a newer/other-owner editor.
|
||||
|
||||
Finite same-author browser observations on29October captured35 DOM/native-store
|
||||
checkpoints: ordinary save/reload, exact retained comparison/copy, peer original
|
||||
preservation with memory-only recovery, new input/storage-event invalidation,
|
||||
held decision with peer changes, account/document/lifetime/expiry boundaries,
|
||||
unknown acknowledgement and actual reload/close/reopen copy inspection, eight-copy
|
||||
capacity with no eviction, quarantine/malformed input and the preserved DR-NAR-001
|
||||
post-commit receipt regression. Timing/acknowledgement loss used explicit local
|
||||
controls; native storage writes/readback and reload were real. Final failure-feedback
|
||||
readback is recorded separately. A sandbox loopback timeout and selector/binding
|
||||
capture errors remain private evidence; already-completed effects were not replayed.
|
||||
Node conflict tests include three source-exposed mutations that pass ordinary
|
||||
save/copy controls before named stale-revision, changed-slot and newer-input
|
||||
assertions reject them. These are deterministic doubles, not extra browser trials.
|
||||
|
||||
To reproduce: checkpoint text, reload, compare the offered checkpoint, confirm
|
||||
and reload again. Inspect exact copy text while the original/slot remain unchanged.
|
||||
In a peer tab restore/edit/save a newer original; compare the old editor memory
|
||||
and explicitly keep it separately. Hold a copy before dispatch, change input,
|
||||
scope or the peer's record/slot, then release: no copy may be written. Use the
|
||||
controlled copy acknowledgement loss, reload and inspect the accepted copy once.
|
||||
No native quota/denial, process restart/crash, real service/auth or parity claim
|
||||
follows. Run `node --test tests/draft-conflicts.test.mjs tests/draft-recovery.test.mjs`.
|
||||
|
||||
1. Save `Confirmed`, reload and observe that saved text. Edit ` Draft Ω <tag> `,
|
||||
wait for **Draft checkpointed; not saved**, reload, and observe `Confirmed` plus
|
||||
**Recovery available**. Restore: exact whitespace/text survives, saved record
|
||||
remains `Confirmed`, status remains unsaved. Close that tab and reopen the same
|
||||
origin; repeat the explicit restore. This is tab reopen, not browser restart.
|
||||
2. Reject next save: prior saved bytes and draft remain. Reload offers recovery.
|
||||
Hold a save before dispatch, inspect unchanged native record, close the tab,
|
||||
reopen and recover the checkpoint. In two tabs restore the same draft; hold
|
||||
the old tab, edit/save `Newer record` in the peer, release the old tab: conflict
|
||||
must preserve the peer's native revision/content. A newer edit during a hold
|
||||
supersedes the captured save and keeps its own checkpoint.
|
||||
3. Hold A/X/1, expire, select B/X/1 and release: the old local effect cannot write
|
||||
or certify B's editor. A/Y/1 and A/X/2 cannot offer A/X/1 recovery. Return explicitly
|
||||
to A/X/1 to recover. Lose an acknowledgement after native write: the editor
|
||||
reports unknown and blocks replay; reload reconciles accepted saved bytes.
|
||||
4. Setup a timestamp of 1 or 9999999999999 on a real checkpoint: restore is blocked
|
||||
without deleting it. Apply `{` or 65537 raw ASCII characters: editor stays hidden
|
||||
and native bytes remain unchanged. Explicit seed setup recovers the lab only.
|
||||
5. **Hold next acknowledgement after native commit**, then Save `Captured save`.
|
||||
Read store must show that actual committed record while feedback stays Saving.
|
||||
Edit `Newer unsaved edit` before releasing the acknowledgement. Release must keep
|
||||
that editor dirty and create its checkpoint against the confirmed revision2,
|
||||
without saving the newer record. Actual reload/explicit restore retains it;
|
||||
normal Save then writes revision3. Repeat with a different simulated account
|
||||
active before release: its editor/checkpoint must stay unchanged. This is an
|
||||
explicit local acknowledgement gate around real native storage, not evidence
|
||||
of a particular uninstrumented browser schedule or external service timing.
|
||||
|
||||
The Read store control performs a fresh native `getItem` and renders exact bytes.
|
||||
Record that plus actual DOM values/visibility, revision, scope, pending flag, URL
|
||||
and native reload/close/reopen actions. Diagnostic labels alone are insufficient.
|
||||
|
||||
## Evidence and limits
|
||||
|
||||
The initial 28 October 2026 proposal captured 41 candidate DOM/native-store checkpoints
|
||||
and 14 checkpoints on three disposable faulty copies. Independent review found a
|
||||
missing post-commit/pre-acknowledgement sequence guard; those results remain historical
|
||||
and do not accept that rejected proposal. The corrected trial captured **52 candidate
|
||||
checkpoints and 20 on four faulty copies**, retaining the three families and adding
|
||||
the acknowledgement regression and another-owner control. Each copy passed seed,
|
||||
ordinary save and actual reload controls before a named mismatch: missing base
|
||||
guard overwrote a newer native record; missing slot scope offered A's draft to B;
|
||||
restore falsely said Saved with unchanged native saved content; and a missing
|
||||
receipt-sequence guard marked newer unsaved text clean after an older commit.
|
||||
The **28 Node tests**
|
||||
include matching faults and injected storage/lock ports; they do not establish
|
||||
browser behavior. A sandbox server timed out; normal-context loopback worked.
|
||||
One browser capture timeout was preserved and recaptured after inspecting the
|
||||
already-completed action, without replaying it. Private raw evidence remains with
|
||||
the task reviewer; these are same-author, source-exposed finite observations.
|
||||
|
||||
Run `node --test tests/draft-recovery.test.mjs`. Native storage denial, crash/power
|
||||
loss, browser-process restart, other browsers/devices, external writers ignoring
|
||||
the shared lock, real service/auth enforcement, original parity, model quality
|
||||
and host instruction reload remain unverified. Injected failure is not native
|
||||
storage denial; controlled acknowledgement loss is not a real remote timeout.
|
||||
Local pre-effect invalidation does not cancel an external service write.
|
||||
|
||||
Mechanism references: [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage),
|
||||
[pagehide limitations](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event),
|
||||
and [Web Locks](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API).
|
||||
These support the mechanism and limitations, not acceptance of this policy.
|
||||
@@ -0,0 +1,65 @@
|
||||
import {Editor,KEY,LOCK,seed} from './policy.mjs';
|
||||
const $=id=>document.getElementById(id);
|
||||
// Access the native storage getter lazily: a native SecurityError is observable.
|
||||
const storage={getItem:key=>localStorage.getItem(key),setItem:(key,value)=>localStorage.setItem(key,value)};
|
||||
const lock=navigator.locks ? (name,fn)=>navigator.locks.request(name,{mode:'exclusive'},fn) : null;
|
||||
const editor=new Editor({storage,lock});
|
||||
let reject=false,hold=false,loseAck=false,release=null,holdAck=false,releaseAck=null,holdCopy=false,releaseCopy=null,loseCopyAck=false;
|
||||
const selected=()=>({account:$('account').value,document:$('document').value,lifetime:Number($('lifetime').value)});
|
||||
function render() {
|
||||
const s=editor.state;
|
||||
$('status').textContent=s.status;
|
||||
$('editor').hidden=!s.scope;
|
||||
$('saved').textContent=s.saved;
|
||||
if($('text').value!==s.text)$('text').value=s.text;
|
||||
$('text').disabled=s.uncertain;
|
||||
$('save').disabled=!s.scope || !!editor.pending || s.uncertain || !s.dirty;
|
||||
$('restore').disabled=!s.scope || !editor.offered || !!editor.pending || s.uncertain;
|
||||
$('discard').disabled=!s.scope || !!editor.pending || s.uncertain;
|
||||
$('release').disabled=!release;
|
||||
$('ackrelease').disabled=!releaseAck;
|
||||
$('compare-memory').disabled=!s.scope || !!editor.pending || s.uncertain || !s.dirty;
|
||||
$('compare-retained').disabled=!s.scope || !!editor.pending || s.uncertain || !editor.offered;
|
||||
$('keep-both').disabled=!editor.choice || !!editor.pending || s.uncertain;
|
||||
$('copyrelease').disabled=!releaseCopy;
|
||||
$('comparison').textContent=editor.choice?JSON.stringify(editor.choice,null,2):'No applicable comparison. Compare again after changes.';
|
||||
$('copies').textContent=JSON.stringify(editor.copies?.map(c=>c.eligible?c:{id:c.id,scope:c.scope,time:c.time,quarantined:true})??[],null,2);
|
||||
$('state').textContent=JSON.stringify({...s,pending:!!editor.pending,offered:editor.offered?{scope:editor.offered.scope,base:editor.offered.base,time:editor.offered.time}:null},null,2);
|
||||
}
|
||||
$('open').onclick=()=>{editor.open(selected());render();};
|
||||
$('latest').onclick=()=>{editor.open(editor.state.scope);render();};
|
||||
$('expire').onclick=()=>{editor.open(null);render();};
|
||||
$('text').oninput=()=>{const done=editor.edit($('text').value);render();done.finally(render);};
|
||||
$('restore').onclick=()=>{editor.restore();render();};
|
||||
for(const id of ['account','document','lifetime'])$(id).onchange=()=>{editor.invalidateChoice();render();};
|
||||
window.addEventListener('storage',event=>{if(event.key===KEY || event.key===null){editor.invalidateChoice();render();}});
|
||||
$('compare-memory').onclick=()=>{editor.compare('memory');render();};
|
||||
$('compare-retained').onclick=()=>{editor.compare('retained');render();};
|
||||
$('copyhold').onclick=()=>{holdCopy=true;$('status').textContent='Next copy: controlled hold before dispatch';};
|
||||
$('copylose').onclick=()=>{loseCopyAck=true;$('status').textContent='Next copy: controlled acknowledgement loss';};
|
||||
$('copyrelease').onclick=()=>{const done=releaseCopy;releaseCopy=null;done?.();render();};
|
||||
$('keep-both').onclick=async()=>{
|
||||
const options={loseAck:loseCopyAck};loseCopyAck=false;
|
||||
if(holdCopy){holdCopy=false;options.beforeDispatch=()=>new Promise(resolve=>{releaseCopy=resolve;render();});}
|
||||
const done=editor.keepBoth(options);render();await done;render();
|
||||
};
|
||||
$('discard').onclick=async()=>{await editor.discard();render();};
|
||||
$('reject').onclick=()=>{reject=true;$('status').textContent='Next save: injected rejection';};
|
||||
$('hold').onclick=()=>{hold=true;$('status').textContent='Next save: controlled hold before dispatch';};
|
||||
$('lose').onclick=()=>{loseAck=true;$('status').textContent='Next save: controlled acknowledgement loss';};
|
||||
$('ackhold').onclick=()=>{holdAck=true;$('status').textContent='Next save: controlled hold after native commit';};
|
||||
$('ackrelease').onclick=()=>{const done=releaseAck;releaseAck=null;done?.();render();};
|
||||
$('release').onclick=()=>{const done=release;release=null;done?.();render();};
|
||||
$('save').onclick=async()=>{
|
||||
const options={reject,loseAck};reject=false;loseAck=false;
|
||||
if(hold){hold=false;options.beforeDispatch=()=>new Promise(resolve=>{release=resolve;render();});}
|
||||
if(holdAck){holdAck=false;options.afterCommit=()=>new Promise(resolve=>{releaseAck=resolve;render();});}
|
||||
const done=editor.save(options);render();await done;render();
|
||||
};
|
||||
$('seed').onclick=()=>{$('raw').value=JSON.stringify(seed());};
|
||||
$('apply').onclick=async()=>{
|
||||
try{if(!lock)throw new Error('Lock unavailable');await lock(LOCK,()=>storage.setItem(KEY,$('raw').value));$('stored').textContent=storage.getItem(KEY);editor.open(selected());render();}
|
||||
catch(e){$('status').textContent='Setup failed: '+e.message;}
|
||||
};
|
||||
$('read').onclick=()=>{try{$('stored').textContent=storage.getItem(KEY)??'(absent)';}catch(e){$('stored').textContent='Read failed: '+e.message;}};
|
||||
editor.open(selected());render();
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html><html lang="en"><meta charset="utf-8"><title>Draft recovery lab</title>
|
||||
<style>body{font:16px system-ui;max-width:860px;margin:2rem auto;padding:0 1rem;background:#f5f5f2;color:#182923}button,select,textarea{font:inherit;margin:.3rem;padding:.5rem}textarea{display:block;width:95%;min-height:5rem}pre{white-space:pre-wrap;overflow-wrap:anywhere;border:1px solid #aaa;padding:1rem}fieldset{margin:1rem 0}#status{font-weight:bold}</style>
|
||||
<h1>Draft recovery lab</h1><p>Synthetic accounts and documents. Local demonstration only; no authentication, service or secure storage. Draft checkpoints are separate from saved records.</p>
|
||||
<label>Simulated account <select id="account"><option>A</option><option>B</option></select></label>
|
||||
<label>Document <select id="document"><option>X</option><option>Y</option></select></label>
|
||||
<label>Simulated lifetime <select id="lifetime"><option>1</option><option>2</option></select></label>
|
||||
<button id="open">Open selected context</button><button id="expire">Expire session</button><button id="latest">Read latest</button>
|
||||
<p id="status" role="status"></p><section id="editor"><p>Last read saved record: <output id="saved"></output></p><label>Editor text<textarea id="text" maxlength="2048"></textarea></label><button id="save">Save record</button><button id="restore">Restore offered draft</button><button id="discard">Discard selected draft</button></section>
|
||||
<fieldset><legend>Explicit conflict recovery</legend><p>Comparison reads exact selected text and latest saved content without replacing either. Values are last-known snapshots. Keep both retains a separate copy in this simulated account, document and lifetime; it leaves the original, all draft slots and editor text unchanged. Maximum eight copies, no automatic removal. Copies use 24-hour eligibility and plaintext local storage.</p><button id="compare-memory">Compare current editor</button><button id="compare-retained">Compare offered checkpoint</button><pre id="comparison"></pre><button id="keep-both">Confirm keep both as separate copy</button><p>Copies from the last explicit context read (read latest/reopen to reconcile). A copy is separate from the saved original; inspecting it never saves the editor.</p><pre id="copies"></pre></fieldset>
|
||||
<fieldset><legend>Controlled copy outcomes</legend><button id="copyhold">Hold next copy before dispatch</button><button id="copyrelease">Release held copy</button><button id="copylose">Lose next copy acknowledgement</button></fieldset>
|
||||
<fieldset><legend>Controlled save outcomes (not native denial)</legend><button id="reject">Reject next save before native write</button><button id="hold">Hold next save before dispatch</button><button id="release">Release held save</button><button id="lose">Lose next acknowledgement</button><button id="ackhold">Hold next acknowledgement after native commit</button><button id="ackrelease">Release held acknowledgement</button></fieldset>
|
||||
<details><summary>Rendered diagnostic state</summary><pre id="state"></pre></details>
|
||||
<fieldset><legend>Disposable setup and observation</legend><p>Setup replaces only this lab's synthetic envelope, under its cooperative lock. Raw input may deliberately be invalid. Read store shows exact native bytes; no storage mutation through browser evaluation is needed.</p><label>Synthetic raw envelope<textarea id="raw"></textarea></label><button id="seed">Put seed in setup field</button><button id="apply">Apply synthetic envelope</button><button id="read">Read store</button><pre id="stored"></pre></fieldset>
|
||||
<script type="module" src="/app.mjs"></script></html>
|
||||
@@ -0,0 +1,197 @@
|
||||
// Proposed local fixture policy. Storage and locking ports are doubles in Node tests.
|
||||
export const KEY = 'companion-draft-recovery-v1';
|
||||
export const LOCK = KEY + ':write';
|
||||
export const TTL = 24 * 60 * 60 * 1000;
|
||||
export const MAX_BYTES = 65536;
|
||||
export const MAX_COPIES = 8;
|
||||
const accounts = ['A', 'B'], documents = ['X', 'Y'];
|
||||
const exact = (value, keys) => value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).sort().join() === [...keys].sort().join();
|
||||
const integer = n => Number.isSafeInteger(n) && n >= 1 && n <= 1000000;
|
||||
const text = s => typeof s === 'string' && s.length <= 2048;
|
||||
export const recordKey = c => c.account + '/' + c.document;
|
||||
export const slotKey = c => recordKey(c) + '/' + c.lifetime;
|
||||
export function context(c) {
|
||||
if (!exact(c, ['account','document','lifetime']) || !accounts.includes(c.account) || !documents.includes(c.document) || ![1,2].includes(c.lifetime)) throw new Error('Invalid context');
|
||||
return {...c};
|
||||
}
|
||||
export function seed() {
|
||||
return {schema:1, records:Object.fromEntries(accounts.flatMap(a => documents.map(d => [a+'/'+d, {revision:1,text:a+d+' saved'}]))), drafts:{}};
|
||||
}
|
||||
export function decode(raw) {
|
||||
if (typeof raw !== 'string' || raw.length > MAX_BYTES || new TextEncoder().encode(raw).length > MAX_BYTES) throw new Error('Invalid or oversized store');
|
||||
const db = JSON.parse(raw);
|
||||
if (!(exact(db,['schema','records','drafts']) || exact(db,['schema','records','drafts','copies'])) || db.schema !== 1 || !exact(db.records,['A/X','A/Y','B/X','B/Y']) || !db.drafts || typeof db.drafts !== 'object' || Array.isArray(db.drafts)) throw new Error('Invalid store schema');
|
||||
for (const r of Object.values(db.records)) if (!exact(r,['revision','text']) || !integer(r.revision) || !text(r.text)) throw new Error('Invalid saved record');
|
||||
if (Object.keys(db.drafts).length > 8) throw new Error('Too many drafts');
|
||||
for (const [key,d] of Object.entries(db.drafts)) {
|
||||
if (!exact(d,['scope','base','text','token','time']) || slotKey(context(d.scope)) !== key || !integer(d.base) || !text(d.text) || typeof d.token !== 'string' || !/^[a-zA-Z0-9-]{1,80}$/.test(d.token) || !Number.isSafeInteger(d.time) || d.time < 0) throw new Error('Invalid draft');
|
||||
}
|
||||
if ('copies' in db) {
|
||||
if (!db.copies || typeof db.copies !== 'object' || Array.isArray(db.copies) || Object.keys(db.copies).length > MAX_COPIES) throw new Error('Invalid copies');
|
||||
const decisions=new Set();
|
||||
for (const [id,c] of Object.entries(db.copies)) {
|
||||
if (!/^[1-8]$/.test(id) || !exact(c,['scope','base','text','source','latest','decision','time']) || !integer(c.base) || !integer(c.latest) || !text(c.text) || !['memory','retained'].includes(c.source) || typeof c.decision!=='string' || !/^[a-zA-Z0-9-]{1,80}$/.test(c.decision) || decisions.has(c.decision) || !Number.isSafeInteger(c.time) || c.time<0) throw new Error('Invalid recovered copy');
|
||||
context(c.scope);decisions.add(c.decision);
|
||||
}
|
||||
}
|
||||
return db;
|
||||
}
|
||||
export const eligible = (draft, now) => !!draft && now >= draft.time && now - draft.time <= TTL;
|
||||
const bytes = d => d === undefined ? null : JSON.stringify(d);
|
||||
export class Editor {
|
||||
constructor({storage,lock,now=Date.now,token=()=>crypto.randomUUID()}) {
|
||||
Object.assign(this,{storage,lock,now,token,epoch:0,sequence:0,queue:Promise.resolve(),state:{status:'Closed',scope:null}});
|
||||
}
|
||||
read() { const raw = this.storage.getItem(KEY); if (raw === null) throw new Error('Store absent; use setup explicitly'); return decode(raw); }
|
||||
async write(change, reject=false, accepted=()=>{}) {
|
||||
if (!this.lock) throw new Error('Lock unavailable');
|
||||
return this.lock(LOCK, () => {
|
||||
const db = this.read(), result = change(db), raw = JSON.stringify(db);
|
||||
decode(raw);
|
||||
if (reject) throw new Error('Injected pre-write rejection');
|
||||
this.storage.setItem(KEY,raw);
|
||||
accepted();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
open(scope) {
|
||||
this.epoch++; this.sequence++; this.pending = null; this.expected = null; this.offered = null; this.choice=null; this.copies=[];
|
||||
this.state = {scope:scope ? context(scope):null,text:'',saved:'',base:null,dirty:false,status:scope?'Loading':'Expired',uncertain:false};
|
||||
if (!scope) return;
|
||||
try {
|
||||
const db=this.read(), saved=db.records[recordKey(scope)], draft=db.drafts[slotKey(scope)];
|
||||
Object.assign(this.state,{text:saved.text,saved:saved.text,base:saved.revision});
|
||||
this.offered = draft ? structuredClone(draft) : null;
|
||||
this.copies=Object.entries(db.copies??{}).filter(([,c])=>slotKey(c.scope)===slotKey(scope)).map(([id,c])=>({id,...structuredClone(c),eligible:eligible(c,this.now())}));
|
||||
this.state.status = draft ? eligible(draft,this.now()) ? 'Recovery available' : 'Draft quarantined: lifetime clock' : 'Ready';
|
||||
} catch(e) {this.state.status='Storage unavailable: '+e.message; this.state.scope=null;}
|
||||
}
|
||||
edit(value) {
|
||||
if (!this.state.scope || this.state.uncertain) return Promise.resolve();
|
||||
this.choice=null;this.state.text=value; this.state.dirty=true; this.state.status='Checkpoint pending';
|
||||
const epoch=this.epoch, sequence=++this.sequence, scope={...this.state.scope}, base=this.state.base;
|
||||
const task = async () => {
|
||||
if (epoch !== this.epoch || sequence !== this.sequence) return;
|
||||
try {
|
||||
const draft={scope,base,text:value,token:this.token(),time:this.now()};
|
||||
await this.write(db=>{
|
||||
if (epoch !== this.epoch || sequence !== this.sequence) throw new Error('Superseded checkpoint');
|
||||
if (bytes(db.drafts[slotKey(scope)]) !== this.expected) throw new Error('Draft slot changed; restore or read latest');
|
||||
db.drafts[slotKey(scope)] = draft;
|
||||
});
|
||||
if (epoch === this.epoch) {this.expected=bytes(draft);this.offered=structuredClone(draft);}
|
||||
if (epoch === this.epoch && sequence === this.sequence) this.state.status='Draft checkpointed; not saved';
|
||||
} catch(e) {if (epoch === this.epoch && sequence === this.sequence) this.state.status='Checkpoint failed; memory only: '+e.message;}
|
||||
};
|
||||
this.queue=this.queue.then(task,task); return this.queue;
|
||||
}
|
||||
restore() {
|
||||
if (!this.state.scope || this.state.uncertain) return;
|
||||
this.choice=null;
|
||||
try {
|
||||
const d=this.read().drafts[slotKey(this.state.scope)];
|
||||
if (!d || bytes(d)!==bytes(this.offered) || !eligible(d,this.now())) throw new Error('No unchanged eligible draft');
|
||||
this.sequence++; this.expected=bytes(d);
|
||||
Object.assign(this.state,{text:d.text,base:d.base,dirty:true,status:'Draft restored; not saved'});
|
||||
} catch(e) {this.state.status='Restore blocked: '+e.message;}
|
||||
}
|
||||
async discard() {
|
||||
if (!this.state.scope || this.pending || this.state.uncertain) return;
|
||||
this.choice=null;
|
||||
const epoch=this.epoch, sequence=this.sequence, scope={...this.state.scope};
|
||||
try {
|
||||
await this.queue;
|
||||
if(epoch!==this.epoch || sequence!==this.sequence) throw new Error('Superseded discard');
|
||||
const selected=this.expected ?? bytes(this.offered);
|
||||
await this.write(db=>{
|
||||
if (epoch!==this.epoch || sequence!==this.sequence || bytes(db.drafts[slotKey(scope)])!==selected) throw new Error('Draft changed');
|
||||
delete db.drafts[slotKey(scope)];
|
||||
});
|
||||
if (epoch===this.epoch && sequence===this.sequence) {this.open(scope);this.state.status='Draft discarded';}
|
||||
} catch(e) {if(epoch===this.epoch && sequence===this.sequence)this.state.status='Discard blocked: '+e.message;}
|
||||
}
|
||||
async save({beforeDispatch=async()=>{},afterCommit=async()=>{},reject=false,loseAck=false}={}) {
|
||||
if (!this.state.scope || this.pending || this.state.uncertain || !this.state.dirty) return;
|
||||
this.choice=null;
|
||||
const epoch=this.epoch, sequence=this.sequence, operation={}; this.pending=operation;
|
||||
try {
|
||||
await this.queue;
|
||||
if(epoch!==this.epoch || sequence!==this.sequence) throw new Error('Superseded save');
|
||||
const scope={...this.state.scope}, payload=this.state.text, base=this.state.base, expected=this.expected;
|
||||
this.state.status='Saving';
|
||||
await beforeDispatch();
|
||||
const revision=await this.write(db=>{
|
||||
if(epoch!==this.epoch || sequence!==this.sequence) throw new Error('Superseded save');
|
||||
if(db.records[recordKey(scope)].revision!==base) throw new Error('Newer saved revision; conflict');
|
||||
if(expected===null || bytes(db.drafts[slotKey(scope)])!==expected || !eligible(db.drafts[slotKey(scope)],this.now())) throw new Error('Draft not checkpointed or changed');
|
||||
if(base>=1000000) throw new Error('Revision limit');
|
||||
db.records[recordKey(scope)]={revision:base+1,text:payload}; delete db.drafts[slotKey(scope)]; return base+1;
|
||||
},reject);
|
||||
await afterCommit();
|
||||
if(epoch!==this.epoch) return;
|
||||
if(loseAck) {this.state.uncertain=true;this.state.status='Unknown acknowledgement; read latest before retry';return;}
|
||||
// A committed effect and its caller's receipt are separate boundaries.
|
||||
// Reconcile the saved base without certifying a newer edit as saved.
|
||||
Object.assign(this.state,{saved:payload,base:revision});
|
||||
if(sequence!==this.sequence) {
|
||||
if(this.expected===expected)this.expected=null;
|
||||
if(bytes(this.offered)===expected)this.offered=null;
|
||||
await this.edit(this.state.text);
|
||||
return;
|
||||
}
|
||||
this.expected=null;this.offered=null;
|
||||
Object.assign(this.state,{saved:payload,base:revision,dirty:false,status:'Saved'});
|
||||
} catch(e) {if(epoch===this.epoch)this.state.status='Not saved: '+e.message;}
|
||||
finally {if(this.pending===operation)this.pending=null;}
|
||||
}
|
||||
invalidateChoice() { this.choice=null;this.choiceVersion=(this.choiceVersion??0)+1; }
|
||||
compare(source='memory') {
|
||||
this.invalidateChoice();
|
||||
if (!this.state.scope || this.pending || this.state.uncertain) return;
|
||||
try {
|
||||
if (!['memory','retained'].includes(source)) throw new Error('Invalid source');
|
||||
const db=this.read(),scope={...this.state.scope},slot=db.drafts[slotKey(scope)],time=this.now();
|
||||
if (source==='memory' && !this.state.dirty) throw new Error('No dirty editor text');
|
||||
if (source==='retained' && (!slot || bytes(slot)!==bytes(this.offered) || !eligible(slot,time))) throw new Error('Offered draft changed or expired; read latest');
|
||||
const payload=source==='memory'?this.state.text:slot.text,base=source==='memory'?this.state.base:slot.base;
|
||||
if (!text(payload) || !integer(base)) throw new Error('Invalid selected text');
|
||||
this.choice={decision:this.token(),version:this.choiceVersion,epoch:this.epoch,sequence:this.sequence,scope,source,text:payload,base,slot:bytes(slot),latest:structuredClone(db.records[recordKey(scope)]),time,
|
||||
retention:source==='retained'?'Retained checkpoint':this.expected!==null && bytes(slot)===this.expected && slot.text===payload && eligible(slot,time)?'Checkpointed editor text':'Memory only; reload can lose this text'};
|
||||
this.state.status='Comparison snapshot; confirm separately to keep both';
|
||||
return structuredClone(this.choice);
|
||||
} catch(e) {this.state.status='Compare blocked: '+e.message;}
|
||||
}
|
||||
async keepBoth({beforeDispatch=async()=>{},afterCommit=async()=>{},reject=false,loseAck=false}={}) {
|
||||
if (!this.choice || !this.state.scope || this.pending || this.state.uncertain) return;
|
||||
const selected=this.choice,epoch=this.epoch,operation={};this.pending=operation;
|
||||
let committed=false;
|
||||
// Consume the decision before any asynchronous boundary. No replay on failure.
|
||||
this.choice=null;
|
||||
try {
|
||||
this.state.status='Keeping separate copy';
|
||||
await beforeDispatch();
|
||||
const id=await this.write(db=>{
|
||||
if(epoch!==this.epoch || selected.version!==this.choiceVersion || selected.sequence!==this.sequence || slotKey(selected.scope)!==slotKey(this.state.scope) || !eligible(selected,this.now())) throw new Error('Decision superseded or expired; compare again');
|
||||
const slot=db.drafts[slotKey(selected.scope)];
|
||||
if(bytes(db.records[recordKey(selected.scope)])!==bytes(selected.latest) || bytes(slot)!==selected.slot || (selected.source==='retained' && !eligible(slot,this.now()))) throw new Error('Saved record or draft changed; compare again');
|
||||
const copies=db.copies??{};
|
||||
if(Object.values(copies).some(c=>c.decision===selected.decision)) throw new Error('Decision already recorded; read copies');
|
||||
const id=String(Math.max(0,...Object.keys(copies).map(Number))+1);
|
||||
if(Number(id)>MAX_COPIES) throw new Error('Copy capacity reached; nothing removed');
|
||||
copies[id]={scope:selected.scope,base:selected.base,text:selected.text,source:selected.source,latest:selected.latest.revision,decision:selected.decision,time:this.now()};
|
||||
db.copies=copies;return id;
|
||||
},reject,()=>{committed=true;});
|
||||
await afterCommit();
|
||||
if(epoch!==this.epoch) return;
|
||||
if(loseAck) {this.state.uncertain=true;this.state.status='Unknown copy acknowledgement; read latest before another decision';return;}
|
||||
// A copy receipt cannot certify the editor or change another pending choice.
|
||||
if(selected.sequence===this.sequence)this.state.status='Recovered copy '+id+' retained; original and draft unchanged; editor not saved';
|
||||
} catch(e) {
|
||||
if(epoch===this.epoch) {
|
||||
if(committed) {this.state.uncertain=true;this.state.status='Unknown copy acknowledgement; read latest before another decision';}
|
||||
else if(selected.sequence===this.sequence)this.state.status='Copy not confirmed; '+selected.retention+'; editor unchanged: '+e.message;
|
||||
}
|
||||
}
|
||||
finally {if(this.pending===operation)this.pending=null;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import http from 'node:http';
|
||||
import fs from 'node:fs';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
const routes=new Map([['/','index.html'],['/app.mjs','app.mjs'],['/policy.mjs','policy.mjs']]);
|
||||
export function createServer() {
|
||||
return http.createServer((req,res)=>{
|
||||
if(req.method!=='GET' || !/^127\.0\.0\.1:\d+$/.test(req.headers.host??'')){res.writeHead(403);res.end('Loopback GET only');return;}
|
||||
const url=new URL(req.url,'http://127.0.0.1'),file=routes.get(url.pathname);
|
||||
if(url.search || !file){res.writeHead(404);res.end('Not found');return;}
|
||||
res.writeHead(200,{'Content-Type':file.endsWith('.html')?'text/html; charset=utf-8':'text/javascript; charset=utf-8','Cache-Control':'no-store','Content-Security-Policy':"default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; connect-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",'X-Content-Type-Options':'nosniff'});
|
||||
res.end(fs.readFileSync(new URL(file,import.meta.url)));
|
||||
});
|
||||
}
|
||||
if(process.argv[1]===fileURLToPath(import.meta.url)){
|
||||
const server=createServer();server.listen(0,'127.0.0.1',()=>console.log(JSON.stringify({pid:process.pid,origin:`http://127.0.0.1:${server.address().port}`})));
|
||||
for(const signal of ['SIGINT','SIGTERM'])process.on(signal,()=>server.close(()=>process.exit(0)));
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
# Offline drag/drop and ordering example
|
||||
|
||||
Run with existing Node 20+ from the repository root:
|
||||
|
||||
```sh
|
||||
node --test tests/drag-drop.test.mjs
|
||||
```
|
||||
|
||||
No packages, network, real clipboard/files, browser, OS drag events, user documents
|
||||
or model calls. `candidate.mjs` is an in-memory operation model; `double.mjs`
|
||||
independently records commit attempts and successful effects and can reject one
|
||||
attempt before any effect. It is not a storage implementation or production
|
||||
transaction. This repository-only example is omitted from standalone installs.
|
||||
|
||||
The same author reasoned literal expectations before the first candidate run,
|
||||
then implemented with full source exposure. `expectations.json` contains 81
|
||||
intermediate snapshots and accumulated attempts/effects in 16 reset scenarios;
|
||||
`identity.json` pins its SHA-256. State is retained within each sequence. Initial
|
||||
freeze and a pre-execution amendment are retained privately: the amendment
|
||||
corrected the derived sorted view after transfer and added a nonempty end contrast.
|
||||
No candidate output generated expectations. This is neither independent acceptance
|
||||
nor captured original-app behavior; separate non-author review remains required.
|
||||
|
||||
## Declared proposed policy
|
||||
|
||||
Lists start as left `[a,b,c]`, right `[x]`, empty `[]`; a and b both display
|
||||
`Same`. Stable IDs bind the source and before/after anchors. Moves retain IDs and
|
||||
values; copies get a fresh `copy-N` ID and retain the value. Removal precedes
|
||||
anchor lookup. An end slot is explicit. Self/adjacent moves with unchanged order
|
||||
produce no effect/history. Preview changes no data. One active gesture ignores
|
||||
a second begin; drop consumes that gesture, so repeated drop has no extra effect.
|
||||
|
||||
Filtered views hide IDs without mutating canonical order; placement is relative
|
||||
to the canonical anchor and preserves hidden items. A sorted view derives label
|
||||
order with ID ties and refuses same-list manual placement, while cross-list
|
||||
transfer remains available. These choices illustrate a profile, not universal
|
||||
filter/sort/drop policy. View updates do not count as canonical data revisions.
|
||||
Drop rechecks the current sorted-view rule before any commit attempt. Enabling
|
||||
sorting after a valid same-list preview refuses both move and copy, leaves data
|
||||
and history unchanged and consumes the gesture. Removing sorting does not
|
||||
implicitly revive an invalid preview: request a new preview while the gesture
|
||||
is still active, or begin a fresh gesture after a refused drop. Filter-only
|
||||
changes retain stable canonical source/anchor intent, including hidden items;
|
||||
cross-list transfer remains available with sorted source/destination views.
|
||||
|
||||
Source-list revision is captured at begin and destination revision at preview.
|
||||
Changes to either before drop reject as stale, including removal of the anchor.
|
||||
Invalid/missing previews reject. Cancel clears tentative state. A known failed
|
||||
attempt leaves lists/history unchanged and consumes the gesture; a fresh gesture
|
||||
can retry. The local double has no unknown/partial outcome. Real delayed writes
|
||||
need applicability and reconciliation beyond this example.
|
||||
|
||||
Each successful operation records one before/after history entry. Undo restores
|
||||
identity/value/order only when the entire board still equals that entry's after
|
||||
state. Later list edits conflict and retain both data and history. This conservative
|
||||
whole-board check is not a rebase algorithm; view settings stay current, revisions
|
||||
advance and copy counters do not rewind. Redo, multi-item gestures, rank storage,
|
||||
new owners and arbitrary history are outside the finite profile.
|
||||
|
||||
The explicit `move` action reaches the same begin/preview/drop contract. No
|
||||
keyboard UI, pointer/touch events, focus, drag payload serialization, permissions,
|
||||
screen-reader announcement, browser hit testing or autoscroll is exercised.
|
||||
|
||||
## Discriminating validation
|
||||
|
||||
Eight ordinary checkpoints cover move/copy and undo. Fourteen actual temporary
|
||||
source mutations must pass those controls, then fail a named intermediate
|
||||
comparison with all earlier checkpoints matching: visible-index identity,
|
||||
same-list offset, end prepending, no-op history, self-drop loss, sorted reorder,
|
||||
filter deletion, stale source/destination, cancel commit, repeated begin rebinding,
|
||||
known-failure application, undo overwrite and explicit-action identity substitution.
|
||||
The checker compares every exposed state field and independently recorded
|
||||
attempts/effects; extra effects cannot hide behind unchanged success feedback.
|
||||
Fixture/import/adapter errors propagate and do not count as successful detections.
|
||||
|
||||
`applicability-expectations.json` adds six separately reasoned regression
|
||||
sequences and 39 literal checkpoints for view changes between preview and drop;
|
||||
`applicability-identity.json` pins them independently. They were frozen before
|
||||
adapting the candidate for non-author finding DD-NAR-001. The original oracle,
|
||||
identity and 81 checkpoints remain unchanged. Checks cover consumed refusals,
|
||||
explicit preview refresh after unsorting, cross-list transfer with both views
|
||||
sorted and filter-only changes hiding the bound source and anchor. A fifteenth
|
||||
source mutant removes the effect-boundary recheck: it passes all 81 historical
|
||||
checkpoints, then fails the new sorted-at-drop state/effect assertion. The retained
|
||||
pre-fix regression failure demonstrated this missing transition; these are still
|
||||
same-author/source-exposed model checks, not independent original-app evidence.
|
||||
|
||||
These finite source-exposed checks establish only this proposed model profile.
|
||||
They do not establish original parity, real input/accessibility behavior, backend
|
||||
authorization/atomicity/concurrency, persistence, crash recovery, safe remote undo
|
||||
or model performance. For another target, recover and freeze its own applicable
|
||||
policy using the [standalone guidance](../../skills/rea-reconstruction-companion/references/drag-drop.md).
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"expectationsSha256": "f3c0d2d788d2634c54711a658cba7873d4a80082a8bae07670e3b384a2964aa9",
|
||||
"cases": 6,
|
||||
"checkpoints": 39,
|
||||
"originalExpectationsSha256": "edd4818560d69d24c3df01ec70f77901057aa3923b0a33bcf065ea3c0da26654",
|
||||
"authorship": "Same-author source-exposed literal regression policy, reasoned before candidate adaptation; no candidate output capture. DD-NAR-001."
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
// A finite proposed operation model, without browser or OS event handling.
|
||||
export function createBoard(initial, recorder) {
|
||||
let lists = structuredClone(initial), drag = null, preview = null, status = 'idle', nextCopy = 1;
|
||||
const revisions = Object.fromEntries(Object.keys(lists).map(k => [k,0])), views = {}, history = [];
|
||||
const owns = owner => Object.hasOwn(lists,owner);
|
||||
const find = (owner,id) => owns(owner) && lists[owner].find(item => item.id === id);
|
||||
function snapshot() {
|
||||
const displayed = Object.fromEntries(Object.entries(views).map(([owner,view]) => {
|
||||
let items = lists[owner].filter(item => !view.hidden.includes(item.id));
|
||||
if (view.sorted) items = [...items].sort((a,b) => a.label < b.label ? -1 : a.label > b.label ? 1 : a.id < b.id ? -1 : 1);
|
||||
return [owner,items.map(item => item.id)];
|
||||
}));
|
||||
return structuredClone({lists,views:displayed,drag:drag && {owner:drag.owner,id:drag.id,mode:drag.mode},
|
||||
preview:preview && {dest:preview.dest,position:preview.position,anchor:preview.anchor,valid:preview.valid},status,history:history.length});
|
||||
}
|
||||
function begin(action) {
|
||||
if (drag) return;
|
||||
if (!find(action.owner,action.id) || !['move','copy'].includes(action.mode)) { status = 'rejected'; return; }
|
||||
drag = {owner:action.owner,id:action.id,mode:action.mode,revision:revisions[action.owner]};
|
||||
preview = null; status = 'dragging';
|
||||
}
|
||||
function aim(action) {
|
||||
if (!drag) return;
|
||||
const {dest,position,anchor} = action;
|
||||
const valid = owns(dest) && ['before','after','end'].includes(position) &&
|
||||
(position === 'end' ? anchor === null : Boolean(find(dest,anchor))) &&
|
||||
!(dest === drag.owner && views[dest]?.sorted);
|
||||
preview = {dest,position,anchor,valid,revision:revisions[dest]}; status = valid ? 'preview' : 'invalid';
|
||||
}
|
||||
function drop() {
|
||||
if (!drag) return;
|
||||
const captured = drag, target = preview; drag = null; preview = null;
|
||||
// A view change does not change canonical revisions. Recheck its current
|
||||
// placement rule at the effect boundary without invalidating filter anchors.
|
||||
if (!target?.valid || (target.dest === captured.owner && views[target.dest]?.sorted)) { status = 'rejected'; return; }
|
||||
if (revisions[captured.owner] !== captured.revision || revisions[target.dest] !== target.revision) { status = 'stale'; return; }
|
||||
const before = structuredClone(lists), after = structuredClone(lists);
|
||||
const item = after[captured.owner].find(item => item.id === captured.id);
|
||||
if (captured.mode === 'move' && captured.owner === target.dest && target.anchor === captured.id) { status = 'unchanged'; return; }
|
||||
let inserted = item;
|
||||
if (captured.mode === 'copy') {
|
||||
while (Object.values(after).flat().some(item => item.id === `copy-${nextCopy}`)) nextCopy++;
|
||||
inserted = {...item,id:`copy-${nextCopy}`};
|
||||
} else after[captured.owner] = after[captured.owner].filter(item => item.id !== captured.id);
|
||||
const destination = after[target.dest];
|
||||
const index = target.position === 'end' ? destination.length : destination.findIndex(item => item.id === target.anchor) + (target.position === 'after' ? 1 : 0);
|
||||
destination.splice(index,0,inserted);
|
||||
if (isDeepStrictEqual(before,after)) { status = 'unchanged'; return; }
|
||||
if (!recorder.commit({kind:captured.mode,lists:after})) { status = 'failed'; return; }
|
||||
lists = after; revisions[captured.owner]++; if (target.dest !== captured.owner) revisions[target.dest]++;
|
||||
if (captured.mode === 'copy') nextCopy++;
|
||||
history.push({before,after:structuredClone(after)}); status = 'committed';
|
||||
}
|
||||
function dispatch(action) {
|
||||
if (!action || typeof action.type !== 'string') throw Error('Invalid fixture action');
|
||||
switch (action.type) {
|
||||
case 'begin': begin(action); break;
|
||||
case 'preview': aim(action); break;
|
||||
case 'drop': drop(); break;
|
||||
case 'cancel': drag = null; preview = null; status = 'cancelled'; break;
|
||||
case 'move': if (!drag) {begin(action); aim(action); drop();} break;
|
||||
case 'view':
|
||||
if (!owns(action.owner) || !Array.isArray(action.hidden) || typeof action.sorted !== 'boolean') throw Error('Invalid fixture view');
|
||||
views[action.owner] = {hidden:[...action.hidden],sorted:action.sorted}; break;
|
||||
case 'external': {
|
||||
if (!owns(action.owner)) throw Error('Invalid fixture owner');
|
||||
const list = lists[action.owner];
|
||||
if (action.remove && find(action.owner,action.remove)) lists[action.owner] = list.filter(item => item.id !== action.remove);
|
||||
else if (action.append && typeof action.append.id === 'string' && typeof action.append.label === 'string' && !Object.values(lists).flat().some(item => item.id === action.append.id)) list.push(structuredClone(action.append));
|
||||
else throw Error('Invalid fixture external edit');
|
||||
revisions[action.owner]++; break;
|
||||
}
|
||||
case 'undo': {
|
||||
if (drag) {status = 'undo-conflict'; break;}
|
||||
const entry = history.at(-1);
|
||||
if (!entry) { status = 'no-history'; break; }
|
||||
if (!isDeepStrictEqual(lists,entry.after)) { status = 'undo-conflict'; break; }
|
||||
if (!recorder.commit({kind:'undo',lists:entry.before})) { status = 'failed'; break; }
|
||||
lists = structuredClone(entry.before); history.pop();
|
||||
for (const owner of Object.keys(revisions)) revisions[owner]++;
|
||||
status = 'undone'; break;
|
||||
}
|
||||
default: throw Error('Unknown fixture action');
|
||||
}
|
||||
return snapshot();
|
||||
}
|
||||
if (!initial || Object.values(lists).some(list => !Array.isArray(list) || list.some(item => typeof item.id !== 'string' || typeof item.label !== 'string')) || new Set(Object.values(lists).flat().map(i => i.id)).size !== Object.values(lists).flat().length) throw Error('Invalid fixture boards');
|
||||
return {dispatch,snapshot};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import fs from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
import {createBoard} from './candidate.mjs';
|
||||
import {recordingEffects} from './double.mjs';
|
||||
export function frozen() {
|
||||
const bytes = fs.readFileSync(new URL('./expectations.json',import.meta.url));
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('./identity.json',import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256) throw Error('Changed frozen oracle');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export function applicability() {
|
||||
const bytes = fs.readFileSync(new URL('./applicability-expectations.json',import.meta.url));
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('./applicability-identity.json',import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256) throw Error('Changed frozen applicability oracle');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export function compare(factory = createBoard,cases = null) {
|
||||
const oracle = frozen(), rows = [];
|
||||
for (const scenario of cases ?? [...oracle.ordinary,...oracle.boundaries]) {
|
||||
const recorder = recordingEffects(), board = factory(structuredClone(oracle.initial),recorder);
|
||||
for (const [step,entry] of scenario.steps.entries()) {
|
||||
const action = structuredClone(entry.action);
|
||||
const actual = action.type === 'fail-next' ? (recorder.failNext(),board.snapshot()) : board.dispatch(action);
|
||||
const observed = recorder.inspect();
|
||||
rows.push({id:scenario.id,step,actual,expected:entry.expected,...observed,
|
||||
expectedAttempts:entry.attempts,expectedEffects:entry.effects,
|
||||
passed:isDeepStrictEqual(actual,entry.expected) && isDeepStrictEqual(observed,{attempts:entry.attempts,effects:entry.effects})});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Independent recording at the effect boundary; accepts repeats so faults
|
||||
// appear as extra effects, not infrastructure exceptions. Not real storage.
|
||||
export function recordingEffects() {
|
||||
const attempts = [], effects = []; let fail = false;
|
||||
return {
|
||||
failNext() { fail = true; },
|
||||
commit(operation) {
|
||||
attempts.push(structuredClone(operation));
|
||||
if (fail) { fail = false; return false; }
|
||||
effects.push(structuredClone(operation)); return true;
|
||||
},
|
||||
inspect() { return structuredClone({attempts,effects}); }
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"expectationsSha256": "edd4818560d69d24c3df01ec70f77901057aa3923b0a33bcf065ea3c0da26654",
|
||||
"cases": 16,
|
||||
"checkpoints": 81,
|
||||
"authorship": "Same-author literal proposed policy, source-exposed; no original target captured."
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# First-use workflow review
|
||||
|
||||
The later [core-loading review](core-loading.md) records a separate finite,
|
||||
source-exposed before/after reference-loading assessment. It preserves these
|
||||
historical judgments without relabelling them as fresh model executions.
|
||||
|
||||
This is a bounded manual review by the same agent that edited the guidance, with source and worked-answer exposure. Each row below was read in its stated context and assessed for subject, objective, authority, next action and output size. The decisions are reviewer judgments, not captured responses from isolated candidate runs. No model API, second agent, service, dependency or automatic evaluator was used. A table of plausible interpretations cannot establish arbitrary models' understanding or host activation.
|
||||
|
||||
## Three paths through existing examples
|
||||
|
||||
| Goal and selected evidence | First useful action and proportional result | Boundary retained |
|
||||
| --- | --- | --- |
|
||||
| Understand Pocket Tasks' save/cancel behavior using the [investigation](reference/WALKTHROUGH.md), particularly W1, W2 and W7 | Identify the selected target and explain saved versus draft state, restart and the observed pending-file collision. Cite those observations and their single-writer limits; add requirements/roadmap only to the extent requested. | Static parsing does not prove runtime behavior. Reading the worked answer is source exposure, not a fresh REA investigation. No candidate implementation is needed. |
|
||||
| Rebuild the selected save/cancel workflow using [workflow evidence](cases.md#workflow), then compare the broader [worked reconstruction](reference/reconstruction/README.md) | State the isolated draft/stored contract, implement that selected feature when requested, and exercise save/cancel/restart and exact text preservation against independent expectations. The broader example shows traceability without requiring every app command for this smaller fixture. | The workflow fixture explicitly excludes real disk persistence. Its checker cannot establish the worked app's filesystem guarantees or whole-app parity. Keep the requested original/improved behavior separate. |
|
||||
| Continue the selected [resume checkpoint](resume/TASK.md) and [executable walkthrough](resume/WALKTHROUGH.md) | Read the checkpoint, target/candidate identity and input-only report, then identify unfinished cap behavior. Retain matching input-validation evidence; reassess target-bound claims on changed-target. Use [selective dependency guidance](../skills/rea-reconstruction-companion/references/checkpoints.md#on-resuming) for larger compositions. | Triage-only tasks do not authorize execution. The executable walkthrough separately demonstrates continuation and historical-byte preservation. Passing an input-only report never completes the function. |
|
||||
|
||||
The walkthrough exposed three documentation gaps: the usage section had no resume entry point; the understand-mode bullet said to deliver a blueprint even though later sections permit compact answers; and progressive loading/contextual interpretation was scattered rather than explicit at the decision point. The README now offers the resume path and explains ordinary-language use and host limits. The skill aligns the mode instruction with proportional outputs, interprets corrections/negation in context, and selects references by the next action. Existing consent, integration, updater, evidence and validation contracts remain applicable. No demonstrated need for a new classifier or checkpoint engine was found.
|
||||
|
||||
## Manual scenario assessment
|
||||
|
||||
Context is part of each input. These are illustrative variations, not an exhaustive phrase catalogue, executable routing tests or required user wording. “Next” names the first useful action, not permission for unrelated external actions. The reviewer checked each row against the revised skill; the interpretation and reason are recorded together so disagreements remain reviewable.
|
||||
|
||||
| ID | Request and context | Manual interpretation, reason and next action |
|
||||
| --- | --- | --- |
|
||||
| U1 | Selected Pocket Tasks artifact: “How does saving work? I might make my own one day.” | Understand save behavior; future aspiration does not authorize code. Inspect selected save evidence and return a compact explanation with gaps. |
|
||||
| U2 | Same artifact: “Can you figure out what survives closing it?” | Investigate persistence/restart, with a focused scenario and explicit limits. “Can you” requests action without requiring the word analyze. |
|
||||
| U3 | Same artifact: “wanna undrestand cancel first, no coding pls” | Understand cancel only despite typos; retain the no-code exclusion. Trace draft/stored changes and answer the question. |
|
||||
| U4 | Same artifact: “Map the main workflows so I can plan a replacement.” | Product specification, no implementation intent. Use applicable blueprint sections and evidence-linked requirements/roadmap. |
|
||||
| U5 | “Build nothing yet; tell me how this selected parser rejects malformed packets.” | Negated build stays investigative. Recover rejection contract and unknowns, not a UI or implementation. |
|
||||
| U6 | Ordinary REA request: “Explain this DLL export.” No reconstruction goal. | Keep the function explanation with base REA; a companion blueprint is unnecessary. Select the export and evidence, not a product map. |
|
||||
| B1 | Selected workflow fixture: “Could you make that save/cancel bit work locally?” Prior turn names the fixture and writable project. | Scoped build request by context. Implement draft/stored transitions and compare independent expected snapshots; no second routine approval. |
|
||||
| B2 | Same fixture: “rebulid just save + cancel, keep restart behaving the same” | Build that workflow despite typo; retain restart parity and bounded in-memory contract. Do not invent filesystem persistence. |
|
||||
| B3 | Selected function: “Only addByte, not the app around it. Make it here.” | Function build. Recover byte/saturation/invalid-input contract and run the existing function checker. The executable carrier does not expand scope. |
|
||||
| B4 | Selected parser: “First explain its format, then implement only the decoder.” | Both objectives are authorized in sequence. Explain the contract, then implement the decoder once evidence suffices; no whole-app rebuild. |
|
||||
| B5 | “I don't need another investigation; implement the selected spec's next slice.” Spec and target are supplied. | Build from specification. Check applicability and slice prerequisites; inspect only consequential gaps before coding. |
|
||||
| B6 | “Match the odd row-selection behavior for compatibility; don't fix it.” Selected selection fixture. | Quirk parity is explicit. Preserve index-based behavior and test the distinguishing sort/filter cases; keep improvement suggestions separate. |
|
||||
| B7 | Same fixture: “Keep the rest, but I want selection to follow the item ID now.” | Explicit selected improvement. Record the changed acceptance profile and compare its distinguishing cases without rewriting original observations. |
|
||||
| B8 | “Make decodeFrame faster, but keep its outputs.” Selected library/function, local candidate destination established. | Scoped build/optimization. Establish correctness and relevant measurements before claiming a speed benefit; retain output compatibility. |
|
||||
| C1 | Earlier broad build: “Actually, only the decoder, and no code yet.” | Current correction supersedes scope and mode. Preserve useful evidence and stop implementation; return the decoder contract. |
|
||||
| C2 | Earlier no-code investigation: “Okay, implement that contract here now.” One contract and destination selected. | Context resolves “that”; switch to scoped build and retain exclusions. Check evidence before the next implementation slice. |
|
||||
| C3 | Earlier feature build: “Not the upload bit; I meant import from disk.” | Scope correction. Record upload as deferred; inspect import evidence and compatibility boundaries rather than continuing the old feature. |
|
||||
| C4 | Earlier selected contract: “Could we eventually build this? For today I just need a plan.” | Plan/specify now. The current time-bound request overrides the build wording; no implementation. |
|
||||
| R1 | Selected unchanged checkpoint: “pick up where I left off” | Resume prior mode/scope; read the checkpoint and relevant identities/report first. Reuse only the report's tested slice and name unfinished work. |
|
||||
| R2 | Same checkpoint: “Same files, finish the cap handling in this project.” | Local continuation is requested. Verify applicability, preserve input-only evidence and implement/test cap behavior rather than restarting the app investigation. |
|
||||
| R3 | “Continue, but this is version 2 now.” New target identity supplied. | Resume with reassessment. Preserve original results as history; inspect the change and stale dependencies before adapting, retaining unaffected work. |
|
||||
| R4 | Earlier checkpoint was specification-only: “Resume the notes from yesterday.” The record is explicitly supplied. | Resume investigation, not build. Read the selected record; do not load unrelated chats or infer implementation authority. |
|
||||
| R5 | Selected composite checkpoint: “Only the scorer setting changed; keep the formatter results if they still apply.” | Check actual dependencies and environment evidence. Retain justified formatter reuse, recheck scorer/integration and leave unknown dependencies explicit. |
|
||||
| A1 | No target/context: “Can you rebuild it?” | Missing subject is consequential. Ask which software or feature/artifact; do not select the nearest process or fixture. |
|
||||
| A2 | Reference app and candidate both selected: “Make it better.” No stated outcome. | Target and action are ambiguous. Ask whether the user wants proposals or changes to the candidate, and which behavior; do not mutate the installed reference. |
|
||||
| A3 | Two checkpoints supplied: “Continue that one.” Neither selected. | Ask which checkpoint. Do not choose by filename recency; independent safe reading can establish their identities. |
|
||||
| A4 | “Replace the current installation with the rebuild.” Existing scope only covers a local candidate. | Installation is a consequential new action. Clarify exact target and replacement/backup authority before mutation; a build request alone is insufficient. |
|
||||
| A5 | Selected upload evidence lacks backend access: “Can you tell me why retries duplicate things?” | Investigate bounded hypotheses from the client trace. Explain missing server evidence and the smallest distinguishing experiment; do not invent rollback or contact a provider. |
|
||||
|
||||
These 28 judgments cover paraphrases, indirect requests, typos, negation, aspirations, selected scopes, conversational references, corrections, continuation and genuine ambiguity. They establish that the guidance has a coherent interpretation for these examples under manual source-exposed review. They do not measure model reliability, prove every phrasing routes correctly, or enforce consent or billing in a host.
|
||||
|
||||
## Executable evidence is separate
|
||||
|
||||
Use the existing harnesses and tests rather than a prose classifier:
|
||||
|
||||
```sh
|
||||
node --test tests/evals.test.mjs tests/selection-evals.test.mjs tests/reference-walkthrough.test.mjs tests/reference-reconstruction.test.mjs tests/resume.test.mjs tests/resume-walkthrough.test.mjs tests/resume-selective.test.mjs
|
||||
```
|
||||
|
||||
These checks execute oracle comparisons and deliberate wrong implementations, replay saved observations, verify candidate runtime independence, and exercise checkpoint history/applicability. They test those contracts and mechanisms, not the natural-language rows above. Actual runs and identities belong in the trial/release record; this command alone is not evidence that they passed. Full source/package/install/upgrade/manifest/validator/CI checks still apply to a release. No new executable test is warranted for merely matching these documentation sentences.
|
||||
|
||||
For an actual future host trial, use the existing [evaluation procedure](README.md#run-an-evaluation) and [review template](review-template.md), supply selected evidence without the worked answers or reviewer oracle, and record real actions, failures and limitations. Run no model evaluation unless its environment is established to add no charge. Installation and explicit invocation help expose instructions; neither guarantees that a host activates them, reloads changes, or correctly interprets arbitrary conversation.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Finite offline form-validation example
|
||||
|
||||
This proposed order-form policy demonstrates values, validation timing,
|
||||
dependencies and submission recovery. It is repository-only; standalone skill
|
||||
installations contain the linked guidance, not these fixtures. Use existing
|
||||
Node.js 20+ with no packages, installation, services, model calls or networking:
|
||||
|
||||
```sh
|
||||
node --test tests/form-validation.test.mjs
|
||||
```
|
||||
|
||||
`expectations.json` holds 69 full literal intermediate checkpoints across 13
|
||||
scenarios and three families. `identity.json` binds its bytes. The same author
|
||||
reasoned and froze these proposed expectations before writing `candidate.mjs`;
|
||||
there was source exposure and no independent original target or oracle author.
|
||||
`check.mjs` compares all returned state plus the submission double's separately
|
||||
recorded calls and effects. It does not derive expectations from the candidate.
|
||||
|
||||
## Selected policy
|
||||
|
||||
| Field or boundary | Proposed behavior |
|
||||
| --- | --- |
|
||||
| Draft | Raw strings retained. Title trims for validation/submission, required with at most 20 UTF-16 code units. Low/high accept exactly one ASCII digit 1–9, high >= low; cross-field message belongs to high. |
|
||||
| Timing | Edit re-evaluates affected rules; local errors display for touched fields or after any submission attempt. Blur marks a field touched. Submit validates all applicable fields even if untouched. Error visibility is separate from validity. |
|
||||
| Hidden address | Delivery requires a trimmed nonempty address. Pickup hides it, retains raw value, clears its visible error/touched state and omits it from payload. Reactivation restores the value and current rules. |
|
||||
| Disabled code | Coupon enables code, required as two letters after trim/uppercase. Disabling clears raw code, error and touched state, and omits it. Re-enabling starts empty. This deliberately differs from hidden address. |
|
||||
| Invalid submission | No submission call or recorded effect. Correctable draft and relevant messages remain. |
|
||||
| Submission | Captures canonical payload/revision and one operation ID; duplicates while pending are ignored. Editing stays possible. The double releases actual callbacks in chosen order. |
|
||||
| Known failure | No double effect. Retain draft and last accepted payload; show useful form/field error for the captured revision. Editing a rejected field (or its controller) clears its server error; unrelated edits retain it. Form message remains until a valid resubmission. |
|
||||
| Success | Double records the captured payload as an in-memory effect. Accepted payload remains separate from live draft. If revision changed during submission, outcome is explicitly for an older draft; stale rejection cannot attach current field/form errors. |
|
||||
|
||||
Ordinary controls cover blur/valid submit/accept and known reject/resubmit/accept
|
||||
(seven checkpoints). Each of 18 disposable source faults first passes all seven,
|
||||
then a named intermediate assertion rejects it. Faults cover premature/missing
|
||||
errors, invalid-effect calls, malformed input, cross-field edits, hidden/disabled
|
||||
policies, normalization, server-error clearing, duplicate calls, accepted payload
|
||||
identity, stale feedback and failure after success. Errors from module loading,
|
||||
fixtures or adapters propagate; they are not counted as detections. Separate
|
||||
checks challenge detached snapshots, invalid fixture actions and comparator
|
||||
coverage. Only owned temporary mutant files are created and cleaned up.
|
||||
|
||||
## Limits
|
||||
|
||||
This is an in-memory state model and recording submission double. Accepted
|
||||
effects are double observations, not storage, backend authorization, durability,
|
||||
real delivery or payment. Failure results are explicit known no-effect responses;
|
||||
timeouts, response loss, uncertain writes and cancellation are not simulated.
|
||||
There is one owner and at most one candidate submission in flight. The double
|
||||
permits extra calls so faults fail actual attempt assertions, not a fake service
|
||||
exception. It supports only the three declared results.
|
||||
|
||||
No browser/DOM, native validation/events, visual layout, keyboard/focus,
|
||||
screen-reader announcement, real serialization, asynchronous field validator,
|
||||
multi-owner lifecycle, arbitrary dependency graph, locale parser or form
|
||||
framework is implemented or verified. Additional grammar, direct service calls,
|
||||
authorization and real backend rejection remain target-specific work.
|
||||
Finite same-author tests do not establish original parity, general security or
|
||||
model performance. The [installed guidance](../../skills/rea-reconstruction-companion/references/form-validation.md)
|
||||
explains how to recover an actual target's policy and integrate existing
|
||||
asynchronous, accessibility and security boundaries without imposing this one.
|
||||
@@ -0,0 +1,81 @@
|
||||
// One proposed finite order-form policy; no DOM, storage, network or authorization.
|
||||
export function createForm(initial, service) {
|
||||
const fields = ['title','method','address','low','high','coupon','code'];
|
||||
if (!initial || Object.keys(initial).length !== fields.length ||
|
||||
fields.some(key => key === 'coupon' ? typeof initial[key] !== 'boolean' : typeof initial[key] !== 'string') ||
|
||||
!['pickup','delivery'].includes(initial.method) || fields.some(key => typeof initial[key] === 'string' && initial[key].length > 80) ||
|
||||
(initial.method === 'pickup' && initial.address !== '') || (!initial.coupon && initial.code !== '')) throw Error('Invalid fixture');
|
||||
let draft = structuredClone(initial), touched = [], attempted = false, serverErrors = {},
|
||||
formError = null, pending = null, accepted = null, outcome = null, status = 'editing', revision = 0, nextId = 1;
|
||||
const active = field => field === 'address' ? draft.method === 'delivery' : field === 'code' ? draft.coupon : true;
|
||||
function validation() {
|
||||
const errors = {};
|
||||
if (!draft.title.trim()) errors.title = 'Required';
|
||||
else if (draft.title.trim().length > 20) errors.title = 'Use at most 20 characters';
|
||||
for (const field of ['low','high']) {
|
||||
if (!draft[field].trim()) errors[field] = 'Required';
|
||||
else if (!/^[1-9]$/.test(draft[field])) errors[field] = 'Use 1-9';
|
||||
}
|
||||
if (!errors.low && !errors.high && Number(draft.high) < Number(draft.low)) errors.high = 'Must be >= low';
|
||||
if (active('address') && !draft.address.trim()) errors.address = 'Required';
|
||||
if (active('code')) {
|
||||
if (!draft.code.trim()) errors.code = 'Required';
|
||||
else if (!/^[A-Z]{2}$/.test(draft.code.trim().toUpperCase())) errors.code = 'Use two letters';
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
function payload() {
|
||||
return {title:draft.title.trim(),method:draft.method,low:Number(draft.low),high:Number(draft.high),
|
||||
...(active('address') ? {address:draft.address.trim()} : {}),
|
||||
...(active('code') ? {code:draft.code.trim().toUpperCase()} : {})};
|
||||
}
|
||||
function snapshot() {
|
||||
const errors = Object.fromEntries(Object.entries(validation()).filter(([field]) => attempted || touched.includes(field)));
|
||||
for (const [field,message] of Object.entries(serverErrors)) if (active(field)) errors[field] = message;
|
||||
return structuredClone({draft,touched,attempted,errors,formError,pending,accepted,outcome,status});
|
||||
}
|
||||
function dispatch(action) {
|
||||
if (!action || typeof action !== 'object') throw Error('Invalid action');
|
||||
if (action.type === 'edit') {
|
||||
const {field,value} = action;
|
||||
if (!fields.includes(field) || !active(field) ||
|
||||
(field === 'coupon' ? typeof value !== 'boolean' : typeof value !== 'string' || value.length > 80) ||
|
||||
(field === 'method' && !['pickup','delivery'].includes(value))) throw Error('Invalid edit');
|
||||
draft[field] = value; revision += 1; delete serverErrors[field];
|
||||
if (field === 'method') delete serverErrors.address;
|
||||
if (field === 'coupon') delete serverErrors.code;
|
||||
if (!active('address')) touched = touched.filter(field => field !== 'address');
|
||||
if (!active('code')) { draft.code = ''; touched = touched.filter(field => field !== 'code'); }
|
||||
if (!pending) status = 'editing';
|
||||
} else if (action.type === 'blur') {
|
||||
if (!fields.includes(action.field) || !active(action.field)) throw Error('Invalid blur');
|
||||
if (!touched.includes(action.field)) touched.push(action.field);
|
||||
} else if (action.type === 'submit') {
|
||||
if (pending) return snapshot();
|
||||
attempted = true;
|
||||
if (Object.keys(validation()).length) { status = 'invalid'; return snapshot(); }
|
||||
// A corrected or explicit resubmission rechecks server policy in the double.
|
||||
serverErrors = {}; formError = null;
|
||||
const operation = {id:nextId++,payload:payload(),revision};
|
||||
pending = structuredClone(operation); status = 'pending';
|
||||
service.start(operation.id,structuredClone(operation.payload),result => {
|
||||
if (!['accept','reject','address-reject'].includes(result)) throw Error('Invalid result');
|
||||
if (!pending || pending.id !== operation.id) return;
|
||||
const applicable = revision === operation.revision;
|
||||
pending = null; outcome = {id:operation.id,result};
|
||||
if (result === 'accept') {
|
||||
accepted = structuredClone(operation.payload);
|
||||
status = applicable ? 'accepted' : 'accepted-older-draft';
|
||||
} else {
|
||||
if (applicable) {
|
||||
formError = result === 'reject' ? 'Try again' : 'Choose another address';
|
||||
if (result === 'address-reject' && active('address')) serverErrors.address = 'Unavailable address';
|
||||
}
|
||||
status = applicable ? 'failed' : 'failed-older-draft';
|
||||
}
|
||||
});
|
||||
} else throw Error('Unknown action');
|
||||
return snapshot();
|
||||
}
|
||||
return {dispatch,snapshot};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import fs from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
import {createForm} from './candidate.mjs';
|
||||
import {recordingSubmission} from './double.mjs';
|
||||
export function frozen() {
|
||||
const bytes = fs.readFileSync(new URL('./expectations.json',import.meta.url));
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('./identity.json',import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256) throw Error('Changed frozen oracle');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export function compare(factory = createForm,cases = null) {
|
||||
const oracle = frozen(), rows = [];
|
||||
for (const scenario of cases ?? [...oracle.ordinary,...oracle.boundaries]) {
|
||||
const service = recordingSubmission(), form = factory(structuredClone(oracle.initial),service);
|
||||
for (const [step,entry] of scenario.steps.entries()) {
|
||||
const action = structuredClone(entry.action);
|
||||
const actual = action.type === 'settle' ? (service.complete(action.result),form.snapshot()) : form.dispatch(action);
|
||||
const observed = service.inspect();
|
||||
rows.push({id:scenario.id,step,actual,expected:entry.expected,...observed,
|
||||
expectedAttempts:entry.attempts,expectedEffects:entry.effects,
|
||||
passed:isDeepStrictEqual(actual,entry.expected) && isDeepStrictEqual(observed,{attempts:entry.attempts,effects:entry.effects})});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Controlled completion and independently recorded calls/effects. No service exists.
|
||||
export function recordingSubmission() {
|
||||
const attempts = [], effects = [];
|
||||
const queued = [];
|
||||
return {
|
||||
start(id,payload,callback) {
|
||||
attempts.push(structuredClone({id,payload}));
|
||||
queued.push({payload:structuredClone(payload),callback});
|
||||
},
|
||||
complete(result) {
|
||||
if (!queued.length || !['accept','reject','address-reject'].includes(result)) throw Error('Invalid completion');
|
||||
const job = queued.shift();
|
||||
if (result === 'accept') effects.push(structuredClone(job.payload));
|
||||
job.callback(result);
|
||||
},
|
||||
inspect:() => structuredClone({attempts,effects}),
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"expectationsSha256": "b0d43010fbbeed5346ec8d8a8ca0212df2c7c12881e80e1161529b8b9154a1c2",
|
||||
"authorship": "Same-author literal proposed states frozen before candidate; source exposed",
|
||||
"originalParity": "unverified",
|
||||
"cases": 13,
|
||||
"checkpoints": 69
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Frozen selected expectations
|
||||
|
||||
Frozen before candidate edits on 2026-10-19 against Pocket Board's historical
|
||||
improved fixture in v1.3.29. Same-author/source-exposed teaching exercise. The
|
||||
historical files, data schema, save/conflict behavior and existing oracles remain
|
||||
unchanged. This is a proposed accessibility improvement, not reference parity.
|
||||
|
||||
| ID | Proposed outcome |
|
||||
| --- | --- |
|
||||
| K1 | Initial Tab order New task, Show, Alpha, Beta and reverse order; Enter opens the editor at Title; Tab reaches Done, Save, Cancel; Space toggles Done; Enter submits. Hidden editor controls are skipped. |
|
||||
| F1 | Cancel restores New task or the current row with the opening task's stable id after rerender. If filtering removed it, New task is the fallback. |
|
||||
| F2 | Async save success/rejection restores the invoking editor control when disabling it lost focus to body. A move elsewhere is respected; feedback is not focused. |
|
||||
| E1 | Blank Title + Enter retains stored bytes, identifies/associates the error, sets invalid state and focuses Title. Nonblank correction clears obsolete error/invalid state; Enter saves trimmed input. |
|
||||
| N1 | Rendered native controls retain their visible labels/names and row title/state. Error association is inspected separately from speech. |
|
||||
| S1 | After earlier success, controlled pre-write rejection reports Save failed with useful alert, retains draft and previous bytes; keyboard retry succeeds. Existing status/alert regions persist. |
|
||||
| V1 | Inspect keyboard focus on Title, Save and restored row/New task at the observed viewport. A focus image/CSS check is bounded visual evidence, not a measured contrast result. |
|
||||
|
||||
Retain create/edit/filter/trim/ID/storage/locking/conflict behavior. Deterministic
|
||||
doubles and targeted mutants check application transitions; live browser inputs
|
||||
establish only selected keyboard/focus behavior. Screen-reader announcements,
|
||||
other devices/browsers, contrast measurements and whole-app WCAG conformance are
|
||||
outside the demonstrated coverage. If unavailable, retain them as unrun and give
|
||||
manual next steps. Stop at these cases or concrete limitations; do not expand to
|
||||
a new widget framework or install assistive/auditing software.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Selected results — 2026-10-19
|
||||
|
||||
Same implementing/reviewing agent, source-exposed and synthetic. No independent
|
||||
participant/model test or real user application. The historical v1.3.29 improved
|
||||
fixture and candidate have separate loopback storage origins and equivalent seed.
|
||||
The profile was frozen before candidate edits; original fixtures were not altered.
|
||||
|
||||
| Case | Actual coverage |
|
||||
| --- | --- |
|
||||
| Baseline distinction | Native browser Tab/Enter opens New task at Title; Tab to Cancel then Enter closes editor and leaves active element BODY. Blank Enter displays Title is required. and focuses Title, but aria-invalid/aria-describedby are absent. Read-only DOM confirms those attributes; this does not establish absent speech. |
|
||||
| K1/F1 | Candidate native Tab/Shift+Tab order and Enter opening, Space checkbox toggle, Cancel returning to recreated row, filtered-away fallback, New task opening and Cancel return observed. |
|
||||
| F2 | Native Enter submit from Title and Save, success/rejection/retry focus retained/recovered. The separate deterministic double covers deliberate focus movement elsewhere during a held save and disappearing copy-recovery control; those two cases are not live browser observations. |
|
||||
| E1/N1 | Blank Enter retains exact stored bytes, focuses Title, sets invalid state and error description; nonblank correction clears obsolete error and Enter writes trimmed title. Rendered names/values and native roles inspected in captured AX/DOM. Native accessibility representations can differ: pressed row buttons are exposed as checkboxes by this host's native AX view. No screen-reader announcement was measured. |
|
||||
| S1 | After success, controlled pre-native-write rejection produces fresh failure feedback and retains draft/store; keyboard retry succeeds. Persistent status/alert semantics inspected. Real storage denial and spoken output unrun. |
|
||||
| V1 | Title, Save and restored-row keyboard focus visually inspected in screenshots at 1280×720 test viewport, with dark 3px outline/3px offset. Selected-row blue styling remains distinct. Other viewports, measured contrast, forced colors and device coverage unrun. |
|
||||
|
||||
[observed.json](observed.json) retains 22 unique checkpoints, read-only projected
|
||||
values, exact stored bytes at relevant boundaries and native AX captures.
|
||||
The reusable auditor recomputes against its literal profile and reports no errors.
|
||||
Two earlier incomplete runs were retained privately: cross-realm array comparison
|
||||
and omission of the empty-state text node from the capture projection. The harness
|
||||
was corrected without changing expected outcomes or application logic; these were
|
||||
not passing runs. A later REPL result-assignment error occurred after the complete
|
||||
report was already persisted; re-auditing that retained report passed, without
|
||||
repeating the successful workflow.
|
||||
|
||||
Fourteen focused tests pass: six application-transition cases, five deliberate
|
||||
application defects rejected by the targeted behavioral assertions, historical
|
||||
byte identity, capture-auditor negative cases, and action-error persistence.
|
||||
Doubles do not establish native keyboard or assistive behavior. Full source/package
|
||||
and release validation are separate project gates, not accessibility certification.
|
||||
|
||||
Screen-reader/device/other-browser checks remain unrun. Follow the practical
|
||||
[manual announcement steps](README.md#bounded-results-and-remaining-checks) using
|
||||
an already available setup if required for a real reconstruction. No blanket
|
||||
WCAG conformance, accessibility-tree-to-speech equivalence, independent-user
|
||||
validation, backend verification or all-device claim is made.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Worked interface accessibility slice
|
||||
|
||||
Pocket Board is the existing owned MIT synthetic teaching interface. This
|
||||
[candidate](app.js) starts from the frozen [historical improved fixture](../browser-improvement/improved/app.js)
|
||||
in v1.3.29 and addresses keyboard focus and error association. All historical
|
||||
reference/parity/improved bytes and their expectations remain unchanged.
|
||||
See the pre-edit [acceptance profile](ACCEPTANCE.md), [observed results](OBSERVED.md)
|
||||
and [conditional skill guidance](../../skills/rea-reconstruction-companion/references/interface-accessibility.md).
|
||||
|
||||
| Dimension | Preserved or deliberately changed |
|
||||
| --- | --- |
|
||||
| Functional/storage | Same schema, trimming, IDs, filtering, save/error strings, safe lock/conflict policy and draft retention. |
|
||||
| Interaction/accessibility | Proposed Cancel return by stable task id, fallback when filtered away, recovery of focus lost while controls are disabled, invalid state/error association and correction clearing. |
|
||||
| Visual | Same layout and visible labels; explicit keyboard-focus outline distinct from selected-row styling. No full visual-parity or measured contrast claim. |
|
||||
| Evidence | Actual selected keyboard/DOM/AX/screenshots separately from deterministic application doubles. No spoken-announcement or independent-user evidence. |
|
||||
|
||||

|
||||
|
||||
## Run without new dependencies
|
||||
|
||||
Use existing Node.js 20+ from the repository root:
|
||||
|
||||
```text
|
||||
node --test tests/interface-accessibility.test.mjs
|
||||
node evals/interface-accessibility/server.mjs
|
||||
```
|
||||
|
||||
The server binds loopback only, with explicit routes and no external assets.
|
||||
Baseline port 41780 and candidate port 41781 isolate disposable localStorage
|
||||
(`pocket-board-v1`). In an existing browser open `/setup.html`, click **Apply store**,
|
||||
then open `/fixture.html` at the same port. Reset before each independent run and
|
||||
confirm Alpha/Open and Beta/Done. `/index.html` excludes instrumentation. Ctrl+C
|
||||
stops the server; no process restart or data durability is claimed.
|
||||
|
||||
The fixture's **Reject next save** injects one rejection before the native storage
|
||||
write. It is not real browser quota/permission denial. The setup's **Read store**
|
||||
exposes the exact disposable bytes; do not use these origins with real tasks.
|
||||
|
||||
[browser-check.mjs](browser-check.mjs) exports `runBrowserChecks(createTab, record)`.
|
||||
Supply an existing supported native-key browser adapter: `pressKey(null,key)`
|
||||
without refocusing a chosen control, `typeText(null,text)`, AX observation,
|
||||
read-only DOM evaluation, setup/fault UI locators and tab close. `createTab(url)`
|
||||
opens a disposable tab; `record(report)` persists partial evidence. No browser
|
||||
is installed or launched by the module. It deliberately requires actual native
|
||||
Tab/Shift+Tab/Enter/Space inputs; a click-only or DOM-key-event substitute cannot
|
||||
establish those results. Unsupported adapter actions leave the run incomplete.
|
||||
|
||||
The replay opens Alpha, toggles/cancels, rejects blank Title, corrects/saves,
|
||||
tests failure after success and retry, then filters away the saved task and
|
||||
checks fallback plus New task/Cancel. Data/fault preparation clicks and filter
|
||||
selection are labeled; keyboard checks use actual keys. Names/error relationships
|
||||
come from rendered inspection, not a speech engine. `auditCaptures(report)`
|
||||
requires the exact 22 checkpoints against [literal expectations](profile.mjs),
|
||||
rejects errors, duplicates, omissions and matching self-approved wrong values.
|
||||
|
||||
The [deterministic double](model-harness.mjs) executes the candidate script and
|
||||
models disabling/hiding/rerender focus transitions for regressions and five
|
||||
targeted defects. It is not a browser, tab-order engine, real Web Lock, AX tree
|
||||
or screen reader. The historical browser-improvement suite separately preserves
|
||||
the earlier storage/conflict coverage; new focus behavior is not inferred from it.
|
||||
|
||||
## Bounded results and remaining checks
|
||||
|
||||
Use [OBSERVED.md](OBSERVED.md) for actual methods/coverage. To check announcements
|
||||
with a user's already available browser and screen reader, repeat open → blank
|
||||
submit → correction → save → injected failure → retry → Cancel. Record the
|
||||
actual spoken label, invalid/error description and status transitions, focus and
|
||||
any duplicate/noisy announcements. Review another device/browser only if required
|
||||
for the user's target. These checks remain unrun here; no assistive/auditor
|
||||
software installation, automation bypass or universal conformance claim follows.
|
||||
@@ -0,0 +1,109 @@
|
||||
// Separate accessibility candidate based on the frozen Pocket Board improved fixture.
|
||||
const ids = ['add','notice','problem','scope','rows','prompt','form','name','complete','invalid','abandon','latest','copy','recovery'];
|
||||
const ui = Object.fromEntries(ids.map(id=>[id,document.getElementById(id)]));
|
||||
const storageKey = 'pocket-board-v1', lockName = 'pocket-board-v1:write';
|
||||
const seed = () => [{id:1,title:'Alpha',done:false},{id:2,title:'Beta',done:true}];
|
||||
function decode(raw) {
|
||||
if (raw === null) return seed();
|
||||
const data = JSON.parse(raw);
|
||||
if (!data || data.version !== 1 || !Array.isArray(data.items)) throw new Error('Invalid store');
|
||||
const seen = new Set();
|
||||
return data.items.map(task=>{
|
||||
if (!task || !Number.isSafeInteger(task.id) || task.id <= 0 || typeof task.title !== 'string' || !task.title.trim() || typeof task.done !== 'boolean' || seen.has(task.id)) throw new Error('Invalid task');
|
||||
seen.add(task.id);return {id:task.id,title:task.title,done:task.done};
|
||||
});
|
||||
}
|
||||
let openerId = null;
|
||||
let records = seed(), baseline, known = false, selected = null, busy = false, conflict = false;
|
||||
function load() {
|
||||
// A successful read of malformed bytes is distinct from an unreadable store.
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
let next;
|
||||
try {next=decode(raw);} catch {next=seed();ui.problem.textContent='Storage could not be read.';}
|
||||
records=next;baseline=raw;known=true;
|
||||
}
|
||||
try {load();} catch {ui.problem.textContent='Storage could not be read.';}
|
||||
function paint() {
|
||||
const nodes=[];
|
||||
for (const task of records) {
|
||||
if (ui.scope.value !== 'all' && task.done !== (ui.scope.value === 'done')) continue;
|
||||
const row=document.createElement('button');row.type='button';row.textContent=`${task.title} — ${task.done ? 'Done' : 'Open'}`;
|
||||
row.id=`task-${task.id}`;row.disabled=busy;row.setAttribute('aria-pressed',String(selected === task.id));row.addEventListener('click',()=>begin(task));nodes.push(row);
|
||||
}
|
||||
ui.rows.replaceChildren(...(nodes.length ? nodes : [document.createTextNode('No tasks.')]));
|
||||
ui.recovery.hidden=!conflict;
|
||||
for (const node of [ui.add,ui.scope,...ui.form.elements,ui.latest,ui.copy]) node.disabled=busy;
|
||||
}
|
||||
function begin(task) {
|
||||
if (busy) return;
|
||||
// Recovery is explicit: selecting another row must not silently discard a conflict draft.
|
||||
if (conflict) {ui.problem.textContent='Keep this draft as a new task, or Cancel before opening another task.';return;}
|
||||
openerId=task?.id ?? null;selected=openerId;ui.form.hidden=false;ui.prompt.hidden=true;
|
||||
ui.name.value=task?.title ?? '';ui.complete.checked=task?.done ?? false;
|
||||
clearInvalid();ui.notice.textContent=task ? 'Editing task' : 'New task';paint();ui.name.focus();
|
||||
}
|
||||
function clearInvalid() {ui.invalid.textContent='';ui.name.setAttribute('aria-invalid','false');}
|
||||
ui.name.addEventListener('input',()=>{if(ui.name.value.trim())clearInvalid();});
|
||||
ui.add.addEventListener('click',()=>begin(null));ui.scope.addEventListener('change',paint);
|
||||
for (const field of [ui.name,ui.complete]) field.addEventListener('input',()=>{
|
||||
if (!busy) ui.notice.textContent=conflict ? 'Conflict — draft kept' : 'Unsaved changes';
|
||||
});
|
||||
ui.abandon.addEventListener('click',()=>{
|
||||
if (busy) return;
|
||||
try {load();} catch {ui.notice.textContent='Read failed — draft kept';ui.problem.textContent='Could not load latest. Draft kept.';return;}
|
||||
selected=null;conflict=false;ui.form.hidden=true;ui.prompt.hidden=false;clearInvalid();ui.notice.textContent='Cancelled';paint();
|
||||
(document.getElementById(`task-${openerId}`) ?? ui.add).focus();
|
||||
});
|
||||
ui.latest.addEventListener('click',()=>{
|
||||
if (busy) return;
|
||||
try {
|
||||
// A malformed latest value must not become a seed snapshot during recovery.
|
||||
const raw=localStorage.getItem(storageKey), next=decode(raw);
|
||||
records=next;baseline=raw;known=true;
|
||||
ui.notice.textContent='Latest loaded — draft kept';ui.problem.textContent='Review latest tasks. Save draft as new, or Cancel and reopen a task.';paint();
|
||||
} catch {ui.notice.textContent='Read failed — draft kept';ui.problem.textContent='Could not load latest. Draft kept.';}
|
||||
});
|
||||
async function save(asNew) {
|
||||
if (busy) return;
|
||||
clearInvalid();
|
||||
const title=ui.name.value.trim(), done=ui.complete.checked;
|
||||
if (!title) {ui.name.setAttribute('aria-invalid','true');ui.invalid.textContent='Title is required.';ui.notice.textContent='Not saved';ui.name.focus();return;}
|
||||
if (conflict && !asNew) {ui.notice.textContent='Conflict — draft kept';ui.problem.textContent='Save draft as new, or Cancel and reopen a task.';return;}
|
||||
const invokingControl=document.activeElement;
|
||||
busy=true;ui.notice.textContent='Saving…';ui.problem.textContent='';paint();
|
||||
try {
|
||||
if (!globalThis.isSecureContext || typeof navigator.locks?.request !== 'function') throw new Error('Locks unavailable');
|
||||
await navigator.locks.request(lockName,{mode:'exclusive'},()=>{
|
||||
const raw=localStorage.getItem(storageKey);
|
||||
if (!asNew && (!known || raw !== baseline)) {
|
||||
conflict=true;ui.notice.textContent='Conflict — draft kept';ui.problem.textContent='Another tab changed stored tasks. Load latest to review; your draft is kept.';return;
|
||||
}
|
||||
// Recovery appends to the latest valid store; ordinary save uses its checked snapshot.
|
||||
const source=asNew ? decode(raw) : records;
|
||||
let id=selected;
|
||||
if (asNew || id === null) {
|
||||
const max=source.reduce((n,task)=>Math.max(n,task.id),0);
|
||||
if (max === Number.MAX_SAFE_INTEGER) throw new Error('No safe ID available');
|
||||
id=max+1;
|
||||
}
|
||||
const task={id,title,done};
|
||||
const next=asNew || selected === null ? [...source,task] : source.map(item=>item.id === id ? task : item);
|
||||
const serialized=JSON.stringify({version:1,items:next});
|
||||
localStorage.setItem(storageKey,serialized);
|
||||
// Publish memory/success only after storage accepts the write.
|
||||
records=next;baseline=serialized;known=true;selected=id;conflict=false;ui.name.value=title;
|
||||
ui.notice.textContent='Saved';ui.problem.textContent='';
|
||||
});
|
||||
} catch {ui.notice.textContent='Save failed';ui.problem.textContent='Save failed. Draft kept; storage or safe tab coordination is unavailable. Retry when available.';}
|
||||
finally {
|
||||
busy=false;paint();
|
||||
// Disabling/hiding controls can drop browser focus. Respect a move elsewhere.
|
||||
if (document.activeElement === document.body) {
|
||||
const target=invokingControl === ui.copy && !conflict ? ui.name : invokingControl;
|
||||
if (target?.isConnected && !target.disabled && target !== document.body) target.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.form.addEventListener('submit',event=>{event.preventDefault();void save(false);});
|
||||
ui.copy.addEventListener('click',()=>{void save(true);});
|
||||
paint();
|
||||
@@ -0,0 +1,65 @@
|
||||
// Supported native-key tab adapter; no browser install, DOM mutation or key simulation.
|
||||
import assert from 'node:assert/strict';
|
||||
import {profile} from './profile.mjs';
|
||||
export const checkpoints=['seed','new-focus','show-focus','alpha-focus','beta-focus','reverse','editor','done-focus','space-toggle','save-focus','cancel-focus','cancel-return','blank','corrected','saved','save-again','rejected','retry','row-return','filtered-fallback','new-editor','new-cancel'];
|
||||
export function auditCaptures(report) {
|
||||
assert.deepEqual(report.errors,[],'Action errors remain failures');
|
||||
assert.deepEqual(report.captures.map(c=>c.label),checkpoints,'Incomplete/duplicate/reordered checkpoints');
|
||||
for(const c of report.captures)assert.deepEqual(c.actual,profile[c.label],c.label);
|
||||
return true;
|
||||
}
|
||||
export async function runBrowserChecks(createTab,record=async()=>{}, {port=41781}={}) {
|
||||
const report={captures:[],errors:[],method:'Actual native browser keys with read-only DOM/AX; source-exposed, same-agent',unrun:['Screen-reader announcements','Other browser/device combinations','Measured contrast and full conformance']};
|
||||
const base=`http://127.0.0.1:${port}`;let control,a;
|
||||
async function capture(label,expected,store) {
|
||||
const ax=await a.getAXState({emit:false});
|
||||
const actual=JSON.parse(JSON.stringify(await a.playwright.evaluate(()=>{
|
||||
const by=id=>document.getElementById(id),el=document.activeElement;
|
||||
return {active:el.id||(el===document.body?'BODY':el.textContent),hidden:by('form').hidden,title:by('name').value,done:by('complete').checked,status:by('notice').textContent,invalid:by('name').getAttribute('aria-invalid'),description:by('name').getAttribute('aria-describedby'),error:by('invalid').textContent,problem:by('problem').textContent,rows:[...by('rows').childNodes].map(n=>n.textContent)};
|
||||
})));
|
||||
const selected=Object.fromEntries(Object.keys(expected).map(k=>[k,actual[k]]));
|
||||
if(store!==undefined){await control.playwright.getByRole('button',{name:'Read store',exact:true}).click();selected.store=await control.playwright.locator('#stored').textContent();expected={...expected,store};}
|
||||
assert.deepEqual(expected,profile[label],'Replay must match frozen acceptance');
|
||||
report.captures.push({label,actual:selected,expected,ax});await record(report);
|
||||
assert.deepEqual(selected,expected,label);
|
||||
}
|
||||
const key=async value=>{await a.pressKey(null,value);};
|
||||
const seed='{"version":1,"items":[{"id":1,"title":"Alpha","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
const saved='{"version":1,"items":[{"id":1,"title":"Corrected","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
const retry='{"version":1,"items":[{"id":1,"title":"Kept draft","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
try {
|
||||
control=await createTab(base+'/setup.html');await control.playwright.getByRole('button',{name:'Apply store',exact:true}).click();await control.getAXState({emit:false});
|
||||
a=await createTab(base+'/fixture.html');await capture('seed',{active:'BODY',hidden:true,rows:['Alpha — Open','Beta — Done']},seed);
|
||||
await key('Tab');await capture('new-focus',{active:'add'});
|
||||
await key('Tab');await capture('show-focus',{active:'scope'});
|
||||
await key('Tab');await capture('alpha-focus',{active:'task-1'});
|
||||
await key('Tab');await capture('beta-focus',{active:'task-2'});
|
||||
await key('shift+Tab');await capture('reverse',{active:'task-1'});
|
||||
await key('Return');await capture('editor',{active:'name',title:'Alpha',hidden:false});
|
||||
await key('Tab');await capture('done-focus',{active:'complete'});
|
||||
await key('space');await capture('space-toggle',{active:'complete',done:true});
|
||||
await key('Tab');await capture('save-focus',{active:'Save'});
|
||||
await key('Tab');await capture('cancel-focus',{active:'abandon'});
|
||||
await key('Return');await capture('cancel-return',{active:'task-1',hidden:true});
|
||||
await key('Return');await key('ctrl+a');await key('BackSpace');await key('Return');
|
||||
await capture('blank',{active:'name',invalid:'true',description:'invalid',error:'Title is required.',status:'Not saved'},seed);
|
||||
await a.typeText(null,' Corrected ');await capture('corrected',{active:'name',invalid:'false',error:'',status:'Unsaved changes'});
|
||||
await key('Return');await capture('saved',{active:'name',title:'Corrected',status:'Saved',problem:''},saved);
|
||||
await key('Tab');await key('Tab');await key('Return');await capture('save-again',{active:'Save',status:'Saved'},saved);
|
||||
await key('shift+Tab');await key('shift+Tab');await key('ctrl+a');await a.typeText(null,'Kept draft');
|
||||
// Only controlled fault preparation uses a click; navigation/retry below are actual keys.
|
||||
await a.playwright.getByRole('button',{name:'Reject next save',exact:true}).click();await a.getAXState({emit:false});
|
||||
await key('shift+Tab');await key('shift+Tab');await key('Return');
|
||||
await capture('rejected',{active:'Save',title:'Kept draft',status:'Save failed',problem:'Save failed. Draft kept; storage or safe tab coordination is unavailable. Retry when available.'},saved);
|
||||
await key('Return');await capture('retry',{active:'Save',status:'Saved',problem:''},retry);
|
||||
await key('Tab');await key('Return');await capture('row-return',{active:'task-1',hidden:true,rows:['Kept draft — Open','Beta — Done']});
|
||||
// Filter preparation is explicit; the removal/save/Cancel path uses keys.
|
||||
await a.playwright.getByLabel('Show',{exact:true}).selectOption('open');await a.getAXState({emit:false});
|
||||
await key('Tab');await key('Return');await key('Tab');await key('space');await key('Tab');await key('Return');await a.getAXState({emit:false});await key('Tab');await key('Return');
|
||||
await capture('filtered-fallback',{active:'add',hidden:true,rows:['No tasks.']});
|
||||
await key('Return');await capture('new-editor',{active:'name',title:'',done:false,hidden:false});
|
||||
await key('Tab');await key('Tab');await key('Tab');await key('Return');await capture('new-cancel',{active:'add',hidden:true});
|
||||
auditCaptures(report);return report;
|
||||
} catch(error) {report.errors.push(String(error));await record(report);throw error;}
|
||||
finally {await a?.close();await control?.close();}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Pocket Board</title>
|
||||
<style>
|
||||
body{margin:32px auto;padding:0 20px;max-width:900px;background:#f3f5f8;color:#17283a;font:16px system-ui,sans-serif}header{display:flex;align-items:center;justify-content:space-between}h1{font-size:28px}h2{font-size:21px}.columns{display:grid;grid-template-columns:1fr 1fr;gap:24px}.panel{border:1px solid #d2d9e2;border-radius:8px;padding:24px;background:#fff}button,input,select{font:inherit;padding:8px}button{cursor:pointer}label{display:block;margin:16px 0 8px}.rows{display:grid;gap:8px;margin-top:20px}.rows button{text-align:left}.rows [aria-pressed=true]{outline:3px solid #376ed1}input[type=text]{width:100%;box-sizing:border-box}[role=alert]{color:#a51919}[hidden]{display:none!important}
|
||||
button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid #17283a;outline-offset:3px}
|
||||
</style></head><body>
|
||||
<header><h1>Pocket Board</h1><button id="add">New task</button></header><p role="status" id="notice">Ready</p><p role="alert" id="problem"></p>
|
||||
<main class="columns"><section class="panel"><h2>Tasks</h2><label for="scope">Show</label><select id="scope"><option value="all">All</option><option value="open">Open</option><option value="done">Done</option></select><div class="rows" id="rows"></div></section>
|
||||
<section class="panel"><h2>Editor</h2><p id="prompt">Select a task or create one.</p><form id="form" hidden novalidate><label for="name">Title</label><input type="text" id="name" aria-describedby="invalid" aria-invalid="false"><label><input id="complete" type="checkbox"> Done</label><p role="alert" id="invalid"></p><button type="submit">Save</button> <button type="button" id="abandon">Cancel</button></form></section></main><aside id="recovery" hidden><h2>Recover your draft</h2><p>The draft stays in the editor until saved or cancelled. Reloading this page loses it.</p><button id="latest" type="button">Load latest (keep draft)</button> <button id="copy" type="button">Save draft as new</button></aside><script src="app.js"></script></body></html>
|
||||
@@ -0,0 +1,36 @@
|
||||
// Deterministic application-transition double. No native keyboard/AX/speech engine.
|
||||
import fs from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
import {environment,tick} from '../browser-improvement/model-harness.mjs';
|
||||
export {environment,tick};
|
||||
export const source=fs.readFileSync(new URL('./app.js',import.meta.url),'utf8');
|
||||
export function app(code=source,env=environment()) {
|
||||
const document={activeElement:null};
|
||||
class Element {
|
||||
constructor(id=''){this.id=id;this.value='';this.checked=false;this._hidden=false;this._disabled=false;this.textContent='';this.children=[];this.attrs={};this.events={};this.isConnected=true;}
|
||||
set hidden(v){this._hidden=v;if(v&&this.elements?.includes(document.activeElement))document.activeElement=document.body;}
|
||||
get hidden(){return this._hidden;}
|
||||
set disabled(v){this._disabled=v;if(v&&document.activeElement===this)document.activeElement=document.body;}
|
||||
get disabled(){return this._disabled;}
|
||||
addEventListener(name,fn){(this.events[name]??=[]).push(fn);}
|
||||
dispatch(name){if(!this.disabled)for(const fn of this.events[name]??[])fn({preventDefault(){}});}
|
||||
setAttribute(k,v){this.attrs[k]=v;}
|
||||
focus(){if(this.isConnected&&!this.disabled)document.activeElement=this;}
|
||||
replaceChildren(...nodes){for(const n of this.children){n.isConnected=false;if(document.activeElement===n)document.activeElement=document.body;}this.children=nodes;}
|
||||
}
|
||||
const ui=Object.fromEntries(['add','notice','problem','scope','rows','prompt','form','name','complete','invalid','abandon','latest','copy','recovery','submit','elsewhere'].map(id=>[id,new Element(id)]));
|
||||
document.body=new Element('BODY');document.activeElement=document.body;
|
||||
document.getElementById=id=>ui[id]??ui.rows.children.find(n=>n.id===id)??null;
|
||||
document.createElement=()=>new Element();document.createTextNode=text=>({textContent:text});
|
||||
ui.scope.value='all';ui.form.elements=[ui.name,ui.complete,ui.submit,ui.abandon];
|
||||
ui.name.attrs['aria-invalid']='false';ui.name.attrs['aria-describedby']='invalid';
|
||||
const storage={getItem(){if(env.store.unreadable)throw Error('Denied');return env.store.raw;},setItem(key,value){if(env.store.reject){env.store.reject=false;throw Error('Controlled rejection');}env.store.raw=value;env.store.writes++;}};
|
||||
vm.runInNewContext(code,{document,localStorage:storage,navigator:{locks:env.locks},isSecureContext:true});
|
||||
return {ui,env,document,
|
||||
open(index){ui.rows.children[index].focus();ui.rows.children[index].dispatch('click');},
|
||||
click(id){ui[id].focus();ui[id].dispatch('click');},
|
||||
input(value){ui.name.value=value;ui.name.dispatch('input');},
|
||||
filter(value){ui.scope.value=value;ui.scope.dispatch('change');},
|
||||
async save(from='name'){ui[from].focus();ui.form.dispatch('submit');await tick();}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
{
|
||||
"captures": [
|
||||
{
|
||||
"label": "seed",
|
||||
"actual": {
|
||||
"active": "BODY",
|
||||
"hidden": true,
|
||||
"rows": [
|
||||
"Alpha — Open",
|
||||
"Beta — Done"
|
||||
],
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Alpha\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"expected": {
|
||||
"active": "BODY",
|
||||
"hidden": true,
|
||||
"rows": [
|
||||
"Alpha — Open",
|
||||
"Beta — Done"
|
||||
],
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Alpha\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t5 text Ready\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t16 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t17 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t20 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html"
|
||||
},
|
||||
{
|
||||
"label": "new-focus",
|
||||
"actual": {
|
||||
"active": "add"
|
||||
},
|
||||
"expected": {
|
||||
"active": "add"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t5 text Ready\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t16 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t17 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t20 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 4 button New task, ID: add"
|
||||
},
|
||||
{
|
||||
"label": "show-focus",
|
||||
"actual": {
|
||||
"active": "scope"
|
||||
},
|
||||
"expected": {
|
||||
"active": "scope"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t5 text Ready\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t16 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t17 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t20 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand"
|
||||
},
|
||||
{
|
||||
"label": "alpha-focus",
|
||||
"actual": {
|
||||
"active": "task-1"
|
||||
},
|
||||
"expected": {
|
||||
"active": "task-1"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t5 text Ready\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t16 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t17 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t20 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 16 checkbox Alpha — Open, Value: 0, ID: task-1"
|
||||
},
|
||||
{
|
||||
"label": "beta-focus",
|
||||
"actual": {
|
||||
"active": "task-2"
|
||||
},
|
||||
"expected": {
|
||||
"active": "task-2"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t5 text Ready\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t16 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t17 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t20 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 17 checkbox Beta — Done, Value: 0, ID: task-2"
|
||||
},
|
||||
{
|
||||
"label": "reverse",
|
||||
"actual": {
|
||||
"active": "task-1"
|
||||
},
|
||||
"expected": {
|
||||
"active": "task-1"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t5 text Ready\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t16 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t17 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t20 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 16 checkbox Alpha — Open, Value: 0, ID: task-1"
|
||||
},
|
||||
{
|
||||
"label": "editor",
|
||||
"actual": {
|
||||
"active": "name",
|
||||
"title": "Alpha",
|
||||
"hidden": false
|
||||
},
|
||||
"expected": {
|
||||
"active": "name",
|
||||
"title": "Alpha",
|
||||
"hidden": false
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Editing task\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Alpha, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t31 button Save\n\t\t\t32 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 29 text field (settable) Title, Value: Alpha, ID: name"
|
||||
},
|
||||
{
|
||||
"label": "done-focus",
|
||||
"actual": {
|
||||
"active": "complete"
|
||||
},
|
||||
"expected": {
|
||||
"active": "complete"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Editing task\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Alpha, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t31 button Save\n\t\t\t32 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete"
|
||||
},
|
||||
{
|
||||
"label": "space-toggle",
|
||||
"actual": {
|
||||
"active": "complete",
|
||||
"done": true
|
||||
},
|
||||
"expected": {
|
||||
"active": "complete",
|
||||
"done": true
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t33 text Unsaved changes\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Alpha, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 1, ID: complete\n\t\t\t31 button Save\n\t\t\t32 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 30 checkbox (settable, integer) Description: Done, Value: 1, ID: complete"
|
||||
},
|
||||
{
|
||||
"label": "save-focus",
|
||||
"actual": {
|
||||
"active": "Save"
|
||||
},
|
||||
"expected": {
|
||||
"active": "Save"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t33 text Unsaved changes\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Alpha, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 1, ID: complete\n\t\t\t31 button Save\n\t\t\t32 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 31 button Save"
|
||||
},
|
||||
{
|
||||
"label": "cancel-focus",
|
||||
"actual": {
|
||||
"active": "abandon"
|
||||
},
|
||||
"expected": {
|
||||
"active": "abandon"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t33 text Unsaved changes\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Alpha, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 1, ID: complete\n\t\t\t31 button Save\n\t\t\t32 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 32 button Cancel, ID: abandon"
|
||||
},
|
||||
{
|
||||
"label": "cancel-return",
|
||||
"actual": {
|
||||
"active": "task-1",
|
||||
"hidden": true
|
||||
},
|
||||
"expected": {
|
||||
"active": "task-1",
|
||||
"hidden": true
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Cancelled\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 0, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 25 checkbox Alpha — Open, Value: 0, ID: task-1"
|
||||
},
|
||||
{
|
||||
"label": "blank",
|
||||
"actual": {
|
||||
"active": "name",
|
||||
"invalid": "true",
|
||||
"description": "invalid",
|
||||
"error": "Title is required.",
|
||||
"status": "Not saved",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Alpha\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"expected": {
|
||||
"active": "name",
|
||||
"invalid": "true",
|
||||
"description": "invalid",
|
||||
"error": "Title is required.",
|
||||
"status": "Not saved",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Alpha\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Not saved\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t31 text Title is required.\n\t\t\t32 button Save\n\t\t\t33 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 29 text field (settable) Title, ID: name"
|
||||
},
|
||||
{
|
||||
"label": "corrected",
|
||||
"actual": {
|
||||
"active": "name",
|
||||
"invalid": "false",
|
||||
"error": "",
|
||||
"status": "Unsaved changes"
|
||||
},
|
||||
"expected": {
|
||||
"active": "name",
|
||||
"invalid": "false",
|
||||
"error": "",
|
||||
"status": "Unsaved changes"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t34 text Unsaved changes\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Alpha — Open, Value: 1, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Corrected , ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t32 button Save\n\t\t\t33 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 29 text field (settable) Title, Value: Corrected , ID: name"
|
||||
},
|
||||
{
|
||||
"label": "saved",
|
||||
"actual": {
|
||||
"active": "name",
|
||||
"title": "Corrected",
|
||||
"status": "Saved",
|
||||
"problem": "",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Corrected\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"expected": {
|
||||
"active": "name",
|
||||
"title": "Corrected",
|
||||
"status": "Saved",
|
||||
"problem": "",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Corrected\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t34 text Saved\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t35 checkbox Corrected — Open, Value: 1, ID: task-1\n\t\t\t36 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Corrected, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t32 button Save\n\t\t\t33 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 29 text field (settable) Title, Value: Corrected, ID: name"
|
||||
},
|
||||
{
|
||||
"label": "save-again",
|
||||
"actual": {
|
||||
"active": "Save",
|
||||
"status": "Saved",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Corrected\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"expected": {
|
||||
"active": "Save",
|
||||
"status": "Saved",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Corrected\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t34 text Saved\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t35 checkbox Corrected — Open, Value: 1, ID: task-1\n\t\t\t36 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Corrected, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t32 button Save\n\t\t\t33 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 32 button Save"
|
||||
},
|
||||
{
|
||||
"label": "rejected",
|
||||
"actual": {
|
||||
"active": "Save",
|
||||
"title": "Kept draft",
|
||||
"status": "Save failed",
|
||||
"problem": "Save failed. Draft kept; storage or safe tab coordination is unavailable. Retry when available.",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Corrected\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"expected": {
|
||||
"active": "Save",
|
||||
"title": "Kept draft",
|
||||
"status": "Save failed",
|
||||
"problem": "Save failed. Draft kept; storage or safe tab coordination is unavailable. Retry when available.",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Corrected\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t34 text Save failed\n\t35 text Save failed. Draft kept; storage or safe tab coordination is unavailable. Retry when available.\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t36 checkbox Corrected — Open, Value: 1, ID: task-1\n\t\t\t37 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Kept draft, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t32 button Save\n\t\t\t33 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 32 button Save"
|
||||
},
|
||||
{
|
||||
"label": "retry",
|
||||
"actual": {
|
||||
"active": "Save",
|
||||
"status": "Saved",
|
||||
"problem": "",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Kept draft\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"expected": {
|
||||
"active": "Save",
|
||||
"status": "Saved",
|
||||
"problem": "",
|
||||
"store": "{\"version\":1,\"items\":[{\"id\":1,\"title\":\"Kept draft\",\"done\":false},{\"id\":2,\"title\":\"Beta\",\"done\":true}]}"
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t34 text Saved\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t35 checkbox Kept draft — Open, Value: 1, ID: task-1\n\t\t\t36 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 container form\n\t\t\t28 text Title\n\t\t\t29 text field (settable) Title, Value: Kept draft, ID: name\n\t\t\t30 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t32 button Save\n\t\t\t33 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 32 button Save"
|
||||
},
|
||||
{
|
||||
"label": "row-return",
|
||||
"actual": {
|
||||
"active": "task-1",
|
||||
"hidden": true,
|
||||
"rows": [
|
||||
"Kept draft — Open",
|
||||
"Beta — Done"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"active": "task-1",
|
||||
"hidden": true,
|
||||
"rows": [
|
||||
"Kept draft — Open",
|
||||
"Beta — Done"
|
||||
]
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Cancelled\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: All, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 (selected) All\n\t\t\t\t13 Open\n\t\t\t\t14 Done\n\t\t15 container rows\n\t\t\t25 checkbox Kept draft — Open, Value: 0, ID: task-1\n\t\t\t26 checkbox Beta — Done, Value: 0, ID: task-2\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t27 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 25 checkbox Kept draft — Open, Value: 0, ID: task-1"
|
||||
},
|
||||
{
|
||||
"label": "filtered-fallback",
|
||||
"actual": {
|
||||
"active": "add",
|
||||
"hidden": true,
|
||||
"rows": [
|
||||
"No tasks."
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"active": "add",
|
||||
"hidden": true,
|
||||
"rows": [
|
||||
"No tasks."
|
||||
]
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Cancelled\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: Open, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 All\n\t\t\t\t13 (selected) Open\n\t\t\t\t14 Done\n\t\t25 text No tasks.\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t26 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 4 button New task, ID: add"
|
||||
},
|
||||
{
|
||||
"label": "new-editor",
|
||||
"actual": {
|
||||
"active": "name",
|
||||
"title": "",
|
||||
"done": false,
|
||||
"hidden": false
|
||||
},
|
||||
"expected": {
|
||||
"active": "name",
|
||||
"title": "",
|
||||
"done": false,
|
||||
"hidden": false
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text New task\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: Open, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 All\n\t\t\t\t13 (selected) Open\n\t\t\t\t14 Done\n\t\t25 text No tasks.\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t26 container form\n\t\t\t27 text Title\n\t\t\t28 text field (settable) Title, ID: name\n\t\t\t29 checkbox (settable, integer) Description: Done, Value: 0, ID: complete\n\t\t\t30 button Save\n\t\t\t31 button Cancel, ID: abandon\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 28 text field (settable) Title, ID: name"
|
||||
},
|
||||
{
|
||||
"label": "new-cancel",
|
||||
"actual": {
|
||||
"active": "add",
|
||||
"hidden": true
|
||||
},
|
||||
"expected": {
|
||||
"active": "add",
|
||||
"hidden": true
|
||||
},
|
||||
"ax": "Browser tab: 8, Title: \"Pocket Board\", URL: \"http://127.0.0.1:41781/fixture.html\".\n0 AXWebArea Pocket Board, URL: 127.0.0.1:41781/fixture.html\n\t1 container\n\t\t2 heading Pocket Board, Value: 1\n\t\t\t3 text Pocket Board\n\t\t4 button New task, ID: add\n\t24 text Cancelled\n\t6 container\n\t\t7 heading Tasks, Value: 2\n\t\t\t8 text Tasks\n\t\t9 text Show\n\t\t10 pop up button (collapsed, settable) Show, Value: Open, ID: scope, Secondary Actions: Expand\n\t\t\t11 menu\n\t\t\t\t12 All\n\t\t\t\t13 (selected) Open\n\t\t\t\t14 Done\n\t\t25 text No tasks.\n\t\t18 heading Editor, Value: 2\n\t\t\t19 text Editor\n\t\t26 text Select a task or create one.\n\t21 container Controlled instrumentation\n\t\t22 text Synthetic pre-native-write rejection only.\n\t\t23 button Reject next save, ID: reject\n\nThe focused UI element is 4 button New task, ID: add"
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"method": "Actual native browser keys with read-only DOM/AX; source-exposed, same-agent",
|
||||
"unrun": [
|
||||
"Screen-reader announcements",
|
||||
"Other browser/device combinations",
|
||||
"Measured contrast and full conformance"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Literal proposed acceptance; kept independent of captures and candidate output.
|
||||
const seed='{"version":1,"items":[{"id":1,"title":"Alpha","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
const saved='{"version":1,"items":[{"id":1,"title":"Corrected","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
const retry='{"version":1,"items":[{"id":1,"title":"Kept draft","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
export const profile={
|
||||
seed:{active:'BODY',hidden:true,rows:['Alpha — Open','Beta — Done'],store:seed},
|
||||
'new-focus':{active:'add'},'show-focus':{active:'scope'},'alpha-focus':{active:'task-1'},'beta-focus':{active:'task-2'},reverse:{active:'task-1'},
|
||||
editor:{active:'name',title:'Alpha',hidden:false},'done-focus':{active:'complete'},'space-toggle':{active:'complete',done:true},'save-focus':{active:'Save'},'cancel-focus':{active:'abandon'},
|
||||
'cancel-return':{active:'task-1',hidden:true},
|
||||
blank:{active:'name',invalid:'true',description:'invalid',error:'Title is required.',status:'Not saved',store:seed},
|
||||
corrected:{active:'name',invalid:'false',error:'',status:'Unsaved changes'},
|
||||
saved:{active:'name',title:'Corrected',status:'Saved',problem:'',store:saved},
|
||||
'save-again':{active:'Save',status:'Saved',store:saved},
|
||||
rejected:{active:'Save',title:'Kept draft',status:'Save failed',problem:'Save failed. Draft kept; storage or safe tab coordination is unavailable. Retry when available.',store:saved},
|
||||
retry:{active:'Save',status:'Saved',problem:'',store:retry},
|
||||
'row-return':{active:'task-1',hidden:true,rows:['Kept draft — Open','Beta — Done']},
|
||||
'filtered-fallback':{active:'add',hidden:true,rows:['No tasks.']},
|
||||
'new-editor':{active:'name',title:'',done:false,hidden:false},'new-cancel':{active:'add',hidden:true}
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,24 @@
|
||||
// Explicit routes for the owned disposable fixture. No remote resources/dependencies.
|
||||
import http from 'node:http';
|
||||
import fs from 'node:fs';
|
||||
const seed='{"version":1,"items":[{"id":1,"title":"Alpha","done":false},{"id":2,"title":"Beta","done":true}]}';
|
||||
const setup=`<!doctype html><html lang="en"><meta charset="utf-8"><title>Disposable storage controls</title><h1>Disposable storage controls</h1><label>Raw store<textarea id="raw"></textarea></label><button id="apply">Apply store</button><button id="read">Read store</button><pre id="stored"></pre><p role="status" id="result"></p><script>raw.value=${JSON.stringify(seed)};apply.onclick=()=>{localStorage.setItem('pocket-board-v1',raw.value);result.textContent='Applied';};read.onclick=()=>{stored.textContent=localStorage.getItem('pocket-board-v1');result.textContent='Read';};</script></html>`;
|
||||
const instrumentation='<aside aria-label="Controlled instrumentation"><p>Synthetic pre-native-write rejection only.</p><button id="reject">Reject next save</button></aside><script src="/fixture.js"></script>';
|
||||
const rejection="const nativeSet=Storage.prototype.setItem;let reject=false;document.getElementById('reject').onclick=()=>{reject=true;};Storage.prototype.setItem=function(key,value){if(reject&&key==='pocket-board-v1'){reject=false;throw Error('Controlled rejection');}return nativeSet.call(this,key,value);};";
|
||||
const servers=[];
|
||||
for(const [target,port] of [['baseline',41780],['candidate',41781]]) {
|
||||
const base=target==='baseline'?new URL('../browser-improvement/improved/',import.meta.url):new URL('./',import.meta.url);
|
||||
const server=http.createServer((req,res)=>{
|
||||
let body,type='text/html';
|
||||
if(req.url==='/setup.html')body=setup;
|
||||
else if(req.url==='/index.html'||req.url==='/fixture.html') {
|
||||
body=fs.readFileSync(new URL('index.html',base),'utf8');
|
||||
if(req.url==='/fixture.html')body=body.replace('<script src="app.js">',instrumentation+'<script src="app.js">');
|
||||
} else if(req.url==='/app.js'){body=fs.readFileSync(new URL('app.js',base));type='text/javascript';}
|
||||
else if(req.url==='/fixture.js'){body=rejection;type='text/javascript';}
|
||||
else {res.writeHead(404);res.end('Not found');return;}
|
||||
res.writeHead(200,{'Content-Type':type+'; charset=utf-8','Cache-Control':'no-store'});res.end(body);
|
||||
});
|
||||
servers.push(server);server.listen(port,'127.0.0.1',()=>console.log(`${target}: http://127.0.0.1:${port}/setup.html`));
|
||||
}
|
||||
process.on('SIGINT',()=>{for(const server of servers)server.close();});
|
||||
@@ -0,0 +1,60 @@
|
||||
# Bounded keyboard-command policy example
|
||||
|
||||
Run with existing Node.js 20+; no dependencies or services:
|
||||
|
||||
```sh
|
||||
node evals/keyboard-commands/check.mjs
|
||||
node --test tests/keyboard-commands.test.mjs
|
||||
```
|
||||
|
||||
This same-author, source-exposed teaching contract freezes 72 complete literal
|
||||
checkpoints in 36 scenarios across input/command, context/applicability and
|
||||
repeat/transition families. The separately reasoned expectations and effect
|
||||
records were hash-bound before writing the candidate; candidate output did not
|
||||
generate the oracle. Five ordinary save shortcut/menu checkpoints precede tests
|
||||
of 16 plausible faulty variants. Named mismatches compare all snapshot fields
|
||||
and the independent recording sink, including duplicate effects invisible in a
|
||||
matching router snapshot. It is not independent user testing or a model trial.
|
||||
|
||||
The selected synthetic profile deliberately uses Ctrl plus logical `s` with no
|
||||
extra modifiers; Meta is not an alias. This is a proposed example policy, not a
|
||||
recommendation for every platform. Physical `code` tracks held keys, never the
|
||||
save character. Composition and already-consumed inputs remain unclaimed. Plain
|
||||
text/unmapped keys have no command; Delete/navigation apply only to the list.
|
||||
Editable input may explicitly save but cannot delete the list selection. A modal
|
||||
blocks background save/navigation/delete; Enter confirms and Escape cancels it.
|
||||
The explicit menu route shares the same applicability guards.
|
||||
|
||||
A routed command is captured in a single volatile pending slot; a second route
|
||||
cannot replace it. Routing claims the synthetic input (`consumed:true`) but
|
||||
performs no sink effect until explicit `commit`. The host adapter is absent:
|
||||
`consumed` calls neither preventDefault nor stopPropagation, and `defaultPrevented`
|
||||
is just supplied data. A stale command can be dropped at commit after having
|
||||
claimed its original input. The decision record describes that latest boundary;
|
||||
it cannot undo an earlier real default-prevention call.
|
||||
|
||||
At commit the full current context must equal the captured focus/dialog/selection/
|
||||
revision/disabled context and still be applicable. A changed-and-restored context
|
||||
is deliberately equivalent under this value-based policy; lifecycle epochs,
|
||||
external ownership and asynchronous dispatch are not modeled. The synchronous
|
||||
recording sink runs immediately after the check with no callback/await boundary.
|
||||
It records synthetic save, navigation, open/close confirmation and deletion
|
||||
calls; these are in-memory effects, not filesystem writes or real deletion.
|
||||
|
||||
Key IDs stand for a host adapter's stable identity for one input. The per-router
|
||||
seen set rejects re-entry even after commit; IDs must be unique across distinct
|
||||
inputs for its lifetime. Seen IDs are never evicted in this finite teaching slice.
|
||||
That is not a production memory/lifetime strategy. Held physical codes suppress
|
||||
one-shot commands even when a repeated record omits `repeat`; navigation remains
|
||||
repeatable. Release removes a held code; blur releases all held codes, drops the
|
||||
pending command and clears focus. Opening/closing a dialog retains held state.
|
||||
Cancel-pending drops uncommitted intent; Escape closes a confirmation dialog.
|
||||
Repeated commit cannot repeat an effect. Unknown harness actions and sink errors
|
||||
propagate rather than being counted as a fault detection or a successful command.
|
||||
|
||||
No native/browser dispatch, propagation/default action, actual IME/dead keys,
|
||||
key layout, focus traversal, assistive technology, OS binding, async effect,
|
||||
persistence, undo engine, real global hook/input injection, clipboard/user data,
|
||||
accounts or external service is exercised. No target behavior was recovered.
|
||||
Use the [portable reference](../../skills/rea-reconstruction-companion/references/keyboard-commands.md)
|
||||
to investigate a selected target and preserve its actual unknowns.
|
||||
@@ -0,0 +1,66 @@
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
// One declared Ctrl-only teaching profile, not a platform shortcut resolver.
|
||||
export function createRouter(initial, effects) {
|
||||
let context = structuredClone(initial), pending = null, decision = {command:null,consumed:false,reason:'unmapped'};
|
||||
const held = new Set(), seen = new Set();
|
||||
const applicable = command => {
|
||||
if (command === 'save') return !context.dialog && context.saveEnabled && context.focus !== 'none';
|
||||
if (command === 'request-delete' || command === 'next') return !context.dialog && context.focus === 'list' && context.selection.length > 0;
|
||||
if (command === 'confirm-delete') return context.dialog && context.focus === 'confirm' && context.selection.length > 0;
|
||||
if (command === 'cancel') return context.dialog;
|
||||
return false;
|
||||
};
|
||||
const snapshot = () => structuredClone({context,pending,held:[...held],seen:[...seen],decision});
|
||||
const reject = reason => { decision = {command:null,consumed:false,reason}; };
|
||||
function route(command, repeat = false) {
|
||||
if (pending) { reject('pending'); return; }
|
||||
if (!applicable(command)) { reject('inapplicable'); return; }
|
||||
if (repeat && command !== 'next') { reject('repeat'); return; }
|
||||
pending = {command,context:structuredClone(context)};
|
||||
decision = {command,consumed:true,reason:'routed'};
|
||||
}
|
||||
return {snapshot,dispatch(action) {
|
||||
if (action.type === 'key') {
|
||||
if (seen.has(action.id)) { reject('duplicate'); return snapshot(); }
|
||||
seen.add(action.id);
|
||||
const repeat = action.repeat || held.has(action.code); held.add(action.code);
|
||||
if (action.composing) reject('composition');
|
||||
else if (action.defaultPrevented) reject('already-consumed');
|
||||
else {
|
||||
let command = null;
|
||||
if (!action.meta && !action.alt && !action.altGraph && !action.shift) {
|
||||
if (action.ctrl && action.key.toLowerCase() === 's') command = 'save';
|
||||
else if (!action.ctrl) {
|
||||
if (action.key === 'Delete') command = 'request-delete';
|
||||
if (action.key === 'ArrowDown') command = 'next';
|
||||
if (action.key === 'Enter' && context.dialog) command = 'confirm-delete';
|
||||
if (action.key === 'Escape' && context.dialog) command = 'cancel';
|
||||
}
|
||||
}
|
||||
if (command) route(command,repeat); else reject('unmapped');
|
||||
}
|
||||
} else if (action.type === 'command') route(action.command);
|
||||
else if (action.type === 'release') held.delete(action.code);
|
||||
else if (action.type === 'context') context = {...context,...structuredClone(action.patch)};
|
||||
else if (action.type === 'cancel-pending') { pending = null; reject('cancelled'); }
|
||||
else if (action.type === 'blur') { pending = null; held.clear(); context.focus = 'none'; reject('blur'); }
|
||||
else if (action.type === 'commit') {
|
||||
if (pending) {
|
||||
const captured = pending; pending = null;
|
||||
if (!isDeepStrictEqual(captured.context,context) || !applicable(captured.command)) {
|
||||
decision = {command:captured.command,consumed:false,reason:'stale'};
|
||||
} else {
|
||||
// No await/callback between this recheck and the synchronous synthetic sink.
|
||||
const command = captured.command;
|
||||
effects.apply(command,structuredClone(context));
|
||||
if (command === 'request-delete') context = {...context,dialog:true,focus:'confirm'};
|
||||
if (command === 'cancel') context = {...context,dialog:false,focus:'list'};
|
||||
if (command === 'confirm-delete') context = {...context,dialog:false,focus:'list',selection:[],revision:context.revision+1};
|
||||
if (command === 'next') context.selection = [{a:'b',b:'c',c:'c'}[context.selection[0]]];
|
||||
decision = {command,consumed:true,reason:'applied'};
|
||||
}
|
||||
}
|
||||
} else throw Error('Unknown fixture action');
|
||||
return snapshot();
|
||||
}};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import fs from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {isDeepStrictEqual} from 'node:util';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {createRouter} from './candidate.mjs';
|
||||
import {recordingEffects} from './double.mjs';
|
||||
export function frozen() {
|
||||
const bytes = fs.readFileSync(new URL('./expectations.json',import.meta.url));
|
||||
const identity = JSON.parse(fs.readFileSync(new URL('./identity.json',import.meta.url)));
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== identity.expectationsSha256) throw Error('Changed frozen oracle');
|
||||
return JSON.parse(bytes);
|
||||
}
|
||||
export function compare(factory = createRouter,cases = frozen().cases,sinkFactory = recordingEffects) {
|
||||
const rows = [];
|
||||
for (const scenario of cases) {
|
||||
const sink = sinkFactory(), router = factory(structuredClone(scenario.initial),sink);
|
||||
for (const [step,entry] of scenario.steps.entries()) {
|
||||
const actual = router.dispatch(structuredClone(entry.action)), effects = sink.inspect();
|
||||
rows.push({id:scenario.id,step,actual,expected:entry.expected,effects,expectedEffects:entry.effects,
|
||||
passed:isDeepStrictEqual(actual,entry.expected) && isDeepStrictEqual(effects,entry.effects)});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const rows = compare();console.log(JSON.stringify({kind:'synthetic-proposed-policy',checkpoints:rows.length,failed:rows.filter(row => !row.passed)},null,2));
|
||||
if (rows.some(row => !row.passed)) process.exitCode = 1;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Independent synthetic sink: records actual calls, never router status labels.
|
||||
export function recordingEffects() {
|
||||
const records = [];
|
||||
return {apply(command,context) {
|
||||
if (command === 'save') records.push({kind:'save',revision:context.revision});
|
||||
else if (command === 'request-delete') records.push({kind:'open-confirm',selection:[...context.selection]});
|
||||
else if (command === 'confirm-delete') records.push({kind:'delete',selection:[...context.selection]});
|
||||
else if (command === 'cancel') records.push({kind:'close-confirm'});
|
||||
else if (command === 'next') records.push({kind:'navigate',selection:[{a:'b',b:'c',c:'c'}[context.selection[0]]]});
|
||||
else throw Error('Unknown synthetic command');
|
||||
},inspect:() => structuredClone(records)};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"frozenUtc": "2026-10-23T00:24:19.525322+00:00",
|
||||
"expectationsSha256": "357058e88b5c5e7522ced4cdca30991387602ac0c8ae76a77c14c066fcafe986",
|
||||
"cases": 36,
|
||||
"checkpoints": 72,
|
||||
"ordinaryCheckpoints": 5,
|
||||
"provenance": "Same-author separately reasoned literal complete contexts, routing, pending/held/seen state and external effects before candidate implementation. No candidate output or transition simulator used."
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# MO-20261018: frozen experiment plan
|
||||
|
||||
Frozen before baseline measurement and candidate implementation. Source-exposed,
|
||||
same-agent synthetic teaching experiment; no independent review or held-out data.
|
||||
Target: `createStore().importBytes(bytes)` from the existing Pocket Ledger
|
||||
reconstruction at commit `b5f0e4debfcbc65d0ead435ec14db5629c551eec`.
|
||||
The original reference, reconstruction, corpus, identity and observations remain
|
||||
unchanged. Its [contract](../import-export/CONTRACT.md) and literal corpus are
|
||||
the acceptance oracle, supplemented by contract-derived boundaries and generated
|
||||
inputs. Agreement alone is insufficient. No installed application is changed.
|
||||
|
||||
## Hypothesis and decision
|
||||
|
||||
Observed source: each title uses `Array.from(title)` followed by `.some()` to
|
||||
count scalars and reject lone surrogates. Hypothesis: materializing/scanning
|
||||
these arrays contributes meaningfully to complete in-memory import cost when
|
||||
titles are long. Investigate with one baseline CPU profile using Node's existing
|
||||
profiler. One candidate only: validate/count scalar values in one iteration,
|
||||
without a temporary character array. No artificial baseline work or other tuning.
|
||||
Retain the candidate as an explicitly experimental variant even if the timing
|
||||
benefit is inconclusive; never replace the historical implementation.
|
||||
|
||||
Accept behavior only if frozen corpus, boundary, generated sequence, export,
|
||||
preservation, startup and caught persistence-fault checks pass. Report performance
|
||||
per workload; prefer the candidate only for workloads with repeatable lower
|
||||
median complete-import elapsed time across both final processes. Overlap,
|
||||
regressions, short batches and run-to-run disagreement limit that inference.
|
||||
No universal speed guarantee, CI timing gate or requirement to produce a speedup.
|
||||
|
||||
## Fixed workloads and measurement boundaries
|
||||
|
||||
`workloads.mjs` constructs exact bytes and expected rows before timing:
|
||||
small = 1 short ASCII row; typical = 8 mixed Unicode rows, 32 scalars/title;
|
||||
stress-ascii = 16 rows, 80 ASCII scalars/title; stress-unicode = 16 rows,
|
||||
80 mixed astral/BMP/control scalars/title. These are synthetic distributions,
|
||||
not measured production traffic. All fit the existing 16-row/8192-byte limits.
|
||||
Adversarial/errors belong to correctness checks, not success-path timing.
|
||||
|
||||
Measure repeated complete imports in an in-memory store, including UTF-8/JSON
|
||||
decode, validation, canonical v2 serialization, state replacement and returned
|
||||
row copies. Consume every returned row's id/title/done into a checksum in the
|
||||
timed loop (includes consumer overhead equally for both). Setup, module loading,
|
||||
input generation, correctness assertions, store construction and JSON output
|
||||
are excluded. No filesystem/network, cold startup or durable import latency is
|
||||
measured. Reuse identical immutable bytes; check bytes/state/output outside
|
||||
timing. New stores per batch, imports within each batch replace identical state.
|
||||
|
||||
Use `performance.now()` elapsed milliseconds. Default Node runtime, no forced
|
||||
GC or JIT flags; record Node/V8/platform/architecture and flags. Ordinary shared
|
||||
host, background load/power/scheduling uncontrolled; no affinity/isolation claim.
|
||||
No memory, allocation-rate, peak RSS, CPU-time or energy measurement/claim.
|
||||
|
||||
## Bounded budget and reporting
|
||||
|
||||
Before candidate: one exploratory baseline run (12 samples/workload) and one
|
||||
instrumented baseline profile (50,000 stress-unicode imports). After candidate
|
||||
and correctness: exactly two final process invocations. No discarded samples,
|
||||
calibration, adaptive repetitions or reruns to seek favorable timings. Execution
|
||||
failures are retained separately; any necessary protocol revision gets a new
|
||||
identity and explicit reason before new measurements.
|
||||
|
||||
Each process: 2 warmup batches per variant/workload, then 12 paired batches;
|
||||
baseline-first on even pairs, candidate-first on odd pairs. Workloads rotate by
|
||||
pair, preserving all samples/order; final process 2 reverses starting order.
|
||||
Batch imports: small 1000, typical 250, each stress 100. Store raw batch elapsed
|
||||
times and checksums. Report min/median/max per-import microseconds and paired
|
||||
candidate/baseline median ratios for each process. These 12 batch observations
|
||||
are not individual-request tail percentiles, confidence intervals or independent
|
||||
trials. Explain batch durations, variability and consumer/instrumentation cost.
|
||||
Keep exploration separate from final results and retain unsuccessful outcomes.
|
||||
|
||||
Public evidence includes source/workload/protocol digests and portable runtime
|
||||
metadata. Raw profiler paths and private host identity stay outside the repo;
|
||||
publish only a sanitized attribution summary. Timing observations apply only to
|
||||
the retained workloads, implementation and runtime; CI correctness on another
|
||||
platform does not become a benchmark on that platform.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Measured optimization: an unsuccessful candidate
|
||||
|
||||
Runnable, model-independent Pocket Ledger import experiment using Node.js 20+
|
||||
and standard libraries only. No package install, service, model API or profiler
|
||||
installation. Same-agent, source-exposed synthetic example and review; neither
|
||||
blind nor independent evaluation. The existing [reconstruction](../import-export/candidate.mjs),
|
||||
[reference and literal oracle](../import-export/README.md) are preserved.
|
||||
|
||||
The candidate removes the temporary character array used for Unicode title
|
||||
validation. It passes the selected behavior tests, but the bounded measurement
|
||||
**does not demonstrate an overall benefit**. Typical and stress medians are
|
||||
higher in both final processes; small-input medians disagree. Keep the original
|
||||
implementation for this workload. Fewer explicit intermediate arrays did not
|
||||
establish faster complete imports or measured lower memory use.
|
||||
|
||||
## Run and inspect
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
node evals/measured-optimization/check.mjs
|
||||
node --test tests/measured-optimization.test.mjs
|
||||
node evals/measured-optimization/summary.mjs
|
||||
```
|
||||
|
||||
The checker needs a writable OS temporary parent and local child-process access
|
||||
for the historical persistence/CLI checks. Set `TEMP`/`TMP` on Windows or `TMPDIR`
|
||||
elsewhere to a disposable writable directory if necessary. A blocked check is
|
||||
unrun/failed environment evidence, never a behavior pass. The standard full
|
||||
suite uses the existing capability preflight; do not bypass it to hide failures.
|
||||
|
||||
To repeat the experiment as a **new** observation, read [PROTOCOL.md](PROTOCOL.md),
|
||||
choose fresh output files outside the repository and retain every invocation:
|
||||
|
||||
```sh
|
||||
node evals/measured-optimization/bench.mjs baseline > baseline-new.json
|
||||
node evals/measured-optimization/profile.mjs > profile-new.json
|
||||
node evals/measured-optimization/bench.mjs final-1 > final-1-new.json
|
||||
node evals/measured-optimization/bench.mjs final-2 > final-2-new.json
|
||||
```
|
||||
|
||||
Use default Node without `NODE_OPTIONS` or runtime flags. Setup/input generation,
|
||||
module loading, stores and assertions are outside timing. Each sample times the
|
||||
same complete in-memory import and consumes every returned row's fields. Raw
|
||||
evidence records all samples, alternating variant order, rotating workload order,
|
||||
workload/source digests, warmup and batch sizes. No adaptive reruns. To summarize
|
||||
a new record, import `summarize` from `summary.mjs` and pass the parsed JSON; it
|
||||
checks the record against these exact sources and workload/order contracts.
|
||||
Changed sources need a new protocol/identity, not overwritten historical results.
|
||||
The checker verifies structure, hashes and arithmetic; it cannot prove a supplied
|
||||
timing was honestly measured or validate arbitrary prose performance claims.
|
||||
|
||||
## Retained observation (2026-10-18)
|
||||
|
||||
Plan was committed as `ec7c16a747cbe88db7a60fb5a01f697c522fc31a` before baseline
|
||||
and candidate. [Baseline](baseline.json) and [CPU attribution](profile.json) are
|
||||
exploratory. Exactly two final processes produced [run 1](final-1.json) and
|
||||
[run 2](final-2.json), without a discarded sample or favorable-result rerun.
|
||||
[summary.json](summary.json) is recomputed/checked from these raw observations.
|
||||
Only Windows / Node v24.21.0 / V8 13.6.233.17-node.53 was measured; see raw metadata
|
||||
for architecture. Default runtime, ordinary shared host, uncontrolled background
|
||||
load/power/scheduling. No other runtime/platform benchmark is claimed.
|
||||
|
||||
Each entry below is min / median / max microseconds per import, derived from
|
||||
12 batch elapsed times. Ratios are the median of 12 paired candidate/baseline
|
||||
batch ratios; they need not equal the ratio of the two marginal medians.
|
||||
|
||||
| Workload | Process | Original µs | Candidate µs | Paired ratio | Candidate faster pairs |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| small | 1 | 1.90 / 2.14 / 2.88 | 1.84 / 2.05 / 2.67 | 0.949 | 8/12 |
|
||||
| small | 2 | 1.91 / 2.12 / 3.29 | 1.90 / 2.17 / 2.70 | 0.985 | 7/12 |
|
||||
| typical | 1 | 9.45 / 10.10 / 12.12 | 9.92 / 10.49 / 14.24 | 1.013 | 5/12 |
|
||||
| typical | 2 | 9.60 / 10.35 / 15.48 | 9.85 / 10.56 / 15.64 | 1.021 | 5/12 |
|
||||
| stress-ascii | 1 | 18.44 / 20.95 / 27.04 | 19.03 / 21.96 / 25.48 | 1.026 | 4/12 |
|
||||
| stress-ascii | 2 | 18.51 / 19.87 / 32.19 | 20.01 / 22.04 / 28.78 | 1.095 | 2/12 |
|
||||
| stress-unicode | 1 | 30.28 / 34.22 / 43.81 | 32.33 / 35.09 / 38.56 | 1.010 | 4/12 |
|
||||
| stress-unicode | 2 | 30.97 / 32.63 / 47.44 | 32.58 / 36.69 / 45.80 | 1.083 | 1/12 |
|
||||
|
||||
The minimum final batch duration was about 1.84 ms. Short batches, overlapping
|
||||
ranges, JIT/GC and two shared-host processes limit inference; these are not
|
||||
individual-request percentiles or statistical confidence intervals. The small
|
||||
case's paired ratios look favorable but marginal medians disagree across runs;
|
||||
do not select that metric alone to advertise success. The sampled profile puts
|
||||
534 hits in baseline `decode`, 206 in `serialize`, 44 in GC and 312 elsewhere;
|
||||
it does not isolate array creation or prove the hypothesized mechanism. No
|
||||
allocation volume, heap delta, peak memory, CPU time or energy was measured.
|
||||
|
||||
## Behavior and limits
|
||||
|
||||
[Correctness](correctness.json): the actual historical checker passes 33 frozen
|
||||
and boundary cases per target, cross-reader exports, validation-failure state
|
||||
preservation, controlled persistence faults, retry and reload. Its historical
|
||||
CLI restart checks remain checks of that CLI; the variant adds no CLI. New checks
|
||||
cover 12 literal Unicode boundaries, all timed workloads, detached state copies
|
||||
and 256 deterministic bounded sequences with contract-derived expected rows or
|
||||
errors, byte preservation, state and exact serialized output comparisons. Tests
|
||||
challenge the actual oracle with corrupt output/destructive rejection and the
|
||||
actual measurement checker with missing/duplicate/reordered samples, wrong
|
||||
identities/workloads/checksums, invalid timing and unsupported resource fields.
|
||||
|
||||
An initial local checker invocation failed on persistence in the default temp
|
||||
parent; selecting an authorized writable disposable temp parent resolved that
|
||||
check. The full-suite default preflight separately observed a denied junction
|
||||
and left full validation unrun. Neither was treated as a product pass or as an
|
||||
explanation of arbitrary later failures. Required CI/package checks are separate
|
||||
from this finite local measurement. No exhaustive equivalence, crash durability,
|
||||
hostile-path safety, real-user distribution, universal speed or platform guarantee.
|
||||
|
||||
Elapsed timing uses Node's existing [performance.now API](https://nodejs.org/docs/latest-v24.x/api/perf_hooks.html#performancenow);
|
||||
the CPU profile uses its local [inspector API](https://nodejs.org/docs/latest-v24.x/api/inspector.html).
|
||||
No noisy timing threshold runs in CI; CI recomputes/checks the saved observations
|
||||
and correctness contracts without generating replacement benchmark results.
|
||||
@@ -0,0 +1,391 @@
|
||||
{
|
||||
"experiment": "MO-20261018",
|
||||
"phase": "baseline",
|
||||
"runtime": {
|
||||
"node": "v24.21.0",
|
||||
"v8": "13.6.233.17-node.53",
|
||||
"platform": "win32",
|
||||
"arch": "x64",
|
||||
"flags": [],
|
||||
"nodeOptions": ""
|
||||
},
|
||||
"identities": {
|
||||
"PROTOCOL.md": "7dfb4c64edc7822a89e5f4d4ec8990c83a3693ecff56d280bd27bdb27f612a86",
|
||||
"workloads.mjs": "7d50995f0cf811e134f74d04a97f051a092efa93a0372632d03d53e404911f6c",
|
||||
"bench.mjs": "f014a06ed2efaf34735738ca5b0b874c1311b261e98846ab1800dc1a10da3c54",
|
||||
"../import-export/candidate.mjs": "7b29d66619d7e44c958d83f9520354a05729105f1f444e6be9bea68a0cfe3f1e",
|
||||
"../import-export/reference.mjs": "9ca2b75875e22aee8f84644b9d1d520fa7da798efb8553e9311211648644e1ec",
|
||||
"../import-export/corpus.json": "624f497538556c74d81c8ce37fb42342a21dfb039693943652fe2c0d580382ef",
|
||||
"../import-export/identity.json": "5ba5a46b352a4df7470052cf6b9acefe0bb490d6d52da81a556f0ea43d849359"
|
||||
},
|
||||
"workloads": [
|
||||
{
|
||||
"id": "small",
|
||||
"iterations": 1000,
|
||||
"bytes": 89,
|
||||
"sha256": "f6dd4ca283d7a3b01b35b65ccfc85ec47580ebbd30c086ce46f4a734145f1c21",
|
||||
"scalarsPerTitle": 4
|
||||
},
|
||||
{
|
||||
"id": "typical",
|
||||
"iterations": 250,
|
||||
"bytes": 750,
|
||||
"sha256": "8e7bb8239f4b3a3d081a5b9b5ba017a55b332f1a3584691cf5aab3419984920f",
|
||||
"scalarsPerTitle": 32
|
||||
},
|
||||
{
|
||||
"id": "stress-ascii",
|
||||
"iterations": 100,
|
||||
"bytes": 1904,
|
||||
"sha256": "39a71f7fc41fa4803bfb66952338cb4ebdaf1774873ee382733b9d03200582c2",
|
||||
"scalarsPerTitle": 80
|
||||
},
|
||||
{
|
||||
"id": "stress-unicode",
|
||||
"iterations": 100,
|
||||
"bytes": 3824,
|
||||
"sha256": "ae1a187974a6b9f1fee3d49227235816bafa3b27d6fae83fd17e7edc0c764764",
|
||||
"scalarsPerTitle": 80
|
||||
}
|
||||
],
|
||||
"warmupBatches": 2,
|
||||
"pairs": 12,
|
||||
"samples": [
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.4181000000000097,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.1888999999999896,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.6561000000000092,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 4.684400000000011,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.419700000000006,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.402699999999996,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.293999999999997,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.2257000000000033,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.074799999999996,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2275999999999954,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.145700000000005,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.459000000000003,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.1180999999999983,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.17110000000001,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.48360000000001,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.986699999999999,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1777000000000015,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.3823000000000008,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.2434999999999974,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2835000000000036,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.5394000000000005,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0156999999999954,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.3800000000000097,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.2881000000000142,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.4809000000000196,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.5152999999999963,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1025999999999954,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.5127999999999986,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.093500000000006,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1539000000000215,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.3704999999999927,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9649,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.060500000000019,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.566100000000006,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8540000000000134,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.375,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.5815000000000055,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.905699999999996,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.56880000000001,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1166000000000054,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8585999999999956,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.224199999999996,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0004000000000133,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.510400000000004,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2787999999999897,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.160500000000013,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.5054999999999836,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.069199999999995,
|
||||
"checksum": 134200
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import path from 'node:path';
|
||||
import { createStore as baseline } from '../import-export/candidate.mjs';
|
||||
import { workloads, consume } from './workloads.mjs';
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
||||
export function identities(candidate = false) {
|
||||
const names = ['PROTOCOL.md', 'workloads.mjs', 'bench.mjs', '../import-export/candidate.mjs',
|
||||
'../import-export/reference.mjs', '../import-export/corpus.json', '../import-export/identity.json'];
|
||||
if (candidate) names.push('candidate.mjs');
|
||||
return Object.fromEntries(names.map(name => [name, hash(fs.readFileSync(path.join(here, name)))]));
|
||||
}
|
||||
export function measure(factory, workload, clock = () => performance.now()) {
|
||||
const store = factory();
|
||||
const original = Buffer.from(workload.bytes);
|
||||
let checksum = 0;
|
||||
const start = clock();
|
||||
for (let i = 0; i < workload.iterations; i++) checksum += consume(store.importBytes(workload.bytes));
|
||||
const elapsedMs = clock() - start;
|
||||
assert.ok(Number.isFinite(elapsedMs) && elapsedMs > 0, 'positive elapsed time');
|
||||
assert.equal(checksum, consume(workload.expected) * workload.iterations, 'output consumption');
|
||||
assert.deepEqual(store.state(), workload.expected, 'committed state');
|
||||
assert.deepEqual(workload.bytes, original, 'input unchanged');
|
||||
return { elapsedMs, checksum };
|
||||
}
|
||||
export async function run(mode) {
|
||||
assert.ok(['baseline', 'final-1', 'final-2'].includes(mode), 'explicit measurement phase');
|
||||
assert.equal(process.env.NODE_OPTIONS ?? '', '', 'unset NODE_OPTIONS for this protocol');
|
||||
assert.deepEqual(process.execArgv, [], 'use the default runtime without flags');
|
||||
const factories = { baseline };
|
||||
if (mode !== 'baseline') factories.candidate = (await import('./candidate.mjs')).createStore;
|
||||
const inputs = workloads();
|
||||
const evidence = { experiment: 'MO-20261018', phase: mode,
|
||||
runtime: { node: process.version, v8: process.versions.v8, platform: process.platform,
|
||||
arch: process.arch, flags: [], nodeOptions: '' },
|
||||
identities: identities(mode !== 'baseline'),
|
||||
workloads: inputs.map(w => ({ id: w.id, iterations: w.iterations, bytes: w.bytes.length,
|
||||
sha256: hash(w.bytes), scalarsPerTitle: Array.from(w.expected[0].title).length })),
|
||||
warmupBatches: 2, pairs: 12, samples: [] };
|
||||
for (const w of inputs) for (const factory of Object.values(factories)) {
|
||||
for (let i = 0; i < 2; i++) measure(factory, w);
|
||||
}
|
||||
for (let pair = 0; pair < 12; pair++) {
|
||||
for (let offset = 0; offset < inputs.length; offset++) {
|
||||
const w = inputs[(pair + offset) % inputs.length];
|
||||
const order = Object.keys(factories);
|
||||
if ((pair + Number(mode === 'final-2')) % 2) order.reverse();
|
||||
for (const variant of order) evidence.samples.push({ pair, workload: w.id, variant,
|
||||
...measure(factories[variant], w) });
|
||||
}
|
||||
}
|
||||
return evidence;
|
||||
}
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
assert.equal(process.argv.length, 3, 'Usage: node bench.mjs baseline|final-1|final-2');
|
||||
console.log(JSON.stringify(await run(process.argv[2]), null, 2));
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Experimental single-pass title validation; historical baseline preserved alongside this directory.
|
||||
// Reconstruction from the frozen synthetic contract; no reference/oracle imports.
|
||||
import fs from 'node:fs';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
function fail(code) { throw Object.assign(new Error(code), { code }); }
|
||||
function fields(value, allowed) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) fail('schema');
|
||||
if (Object.keys(value).length !== allowed.length || allowed.some(key => !Object.hasOwn(value, key))) fail('schema');
|
||||
}
|
||||
function decode(bytes) {
|
||||
if (bytes.byteLength > 8192) fail('size');
|
||||
if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) fail('encoding');
|
||||
let source;
|
||||
try { source = new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { fail('encoding'); }
|
||||
let data;
|
||||
try { data = JSON.parse(source); } catch { fail('syntax'); }
|
||||
fields(data, ['format', 'version', 'entries']);
|
||||
if (data.format !== 'pocket-ledger' || typeof data.version !== 'number') fail('schema');
|
||||
if (data.version !== 1 && data.version !== 2) fail('unsupported-version');
|
||||
if (!Array.isArray(data.entries) || data.entries.length > 16) fail('schema');
|
||||
const result = [], ids = new Set();
|
||||
for (const record of data.entries) {
|
||||
fields(record, data.version === 2 ? ['id', 'title', 'done'] : ['id', 'text']);
|
||||
const id = record.id, title = data.version === 2 ? record.title : record.text;
|
||||
if (typeof id !== 'string' || id.length < 1 || id.length > 16 || id[0] < 'a' || id[0] > 'z' ||
|
||||
[...id].some(ch => !'abcdefghijklmnopqrstuvwxyz0123456789-'.includes(ch))) fail('schema');
|
||||
if (typeof title !== 'string') fail('schema');
|
||||
let count = 0;
|
||||
for (const ch of title) {
|
||||
const point = ch.codePointAt(0);
|
||||
if (++count > 80 || (point >= 0xd800 && point <= 0xdfff)) fail('schema');
|
||||
}
|
||||
if (count === 0) fail('schema');
|
||||
if (data.version === 2 && typeof record.done !== 'boolean') fail('schema');
|
||||
if (ids.has(id)) fail('duplicate-id');
|
||||
ids.add(id);
|
||||
result.push({ id, title, done: data.version === 2 ? record.done : false });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function serialize(records, version) {
|
||||
if (version !== 1 && version !== 2) fail('unsupported-version');
|
||||
const entries = [];
|
||||
for (const record of records) {
|
||||
if (version === 1 && record.done) fail('lossy-version');
|
||||
entries.push(version === 1 ? { id: record.id, text: record.title }
|
||||
: { id: record.id, title: record.title, done: record.done });
|
||||
}
|
||||
const output = Buffer.from(JSON.stringify({ format: 'pocket-ledger', version, entries }) + '\n', 'utf8');
|
||||
if (output.length > 8192) fail('size');
|
||||
return output;
|
||||
}
|
||||
function readBounded(file) {
|
||||
const descriptor = fs.openSync(file, 'r');
|
||||
try {
|
||||
const buffer = Buffer.alloc(8193);
|
||||
let used = 0, count;
|
||||
while (used < buffer.length && (count = fs.readSync(descriptor, buffer, used, buffer.length - used, null))) used += count;
|
||||
if (used > 8192) fail('size');
|
||||
return buffer.subarray(0, used);
|
||||
} finally { fs.closeSync(descriptor); }
|
||||
}
|
||||
export function createStore({ file, beforeReplace = () => {} } = {}) {
|
||||
let committed = [];
|
||||
if (file) {
|
||||
try { committed = decode(readBounded(file)); }
|
||||
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
||||
}
|
||||
const api = {
|
||||
state() { return committed.map(row => ({ ...row })); },
|
||||
exportBytes(version = 2) { return serialize(committed, version); },
|
||||
importBytes(bytes) {
|
||||
const staged = decode(bytes), encoded = serialize(staged, 2);
|
||||
if (file) {
|
||||
const sibling = `${file}.${randomUUID()}.tmp`;
|
||||
try {
|
||||
fs.writeFileSync(sibling, encoded, { flag: 'wx' });
|
||||
beforeReplace(sibling, file);
|
||||
fs.renameSync(sibling, file);
|
||||
} catch {
|
||||
try { fs.unlinkSync(sibling); } catch { /* best-effort temporary cleanup */ }
|
||||
fail('persistence');
|
||||
}
|
||||
}
|
||||
committed = staged;
|
||||
return api.state();
|
||||
}
|
||||
};
|
||||
return api;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createStore } from './candidate.mjs';
|
||||
import { createStore as baseline } from '../import-export/candidate.mjs';
|
||||
import { runTrial } from '../import-export/check.mjs';
|
||||
import { encode, workloads } from './workloads.mjs';
|
||||
|
||||
const attempt = (store, bytes) => {
|
||||
try { return { rows: store.importBytes(bytes) }; }
|
||||
catch (error) { return { error: error.code }; }
|
||||
};
|
||||
export function check(factory = createStore) {
|
||||
// Actual historical checker, literal corpus and contract-derived boundaries;
|
||||
// injected/new factory is used by its in-process parity and persistence checks.
|
||||
// Its CLI restart checks still exercise the historical CLI, not a new CLI.
|
||||
const historical = runTrial({ candidateFactory: factory });
|
||||
for (const w of workloads()) {
|
||||
const store = factory();
|
||||
assert.deepEqual(store.importBytes(w.bytes), w.expected);
|
||||
assert.deepEqual(store.state(), w.expected);
|
||||
const detached = store.state(); detached[0].title = 'Changed externally';
|
||||
assert.deepEqual(store.state(), w.expected);
|
||||
assert.deepEqual(JSON.parse(store.exportBytes()).entries, w.expected);
|
||||
}
|
||||
// Literal Unicode boundary oracle, including valid surrogate pairs versus
|
||||
// isolated high/low surrogates and their positions around the scalar limit.
|
||||
const cases = [
|
||||
['', false], ['a'.repeat(80), true], ['a'.repeat(81), false],
|
||||
['😀'.repeat(80), true], ['😀'.repeat(81), false], ['\ud800', false],
|
||||
['\udfff', false], ['a'.repeat(79) + '\ud800', false],
|
||||
['😀'.repeat(79) + '雪', true], ['\ud800\udc00', true],
|
||||
['a\udc00z', false], ['\u0000\n<&', true],
|
||||
];
|
||||
let generated = 0;
|
||||
const a = baseline(), b = factory();
|
||||
const prior = [{ id: 'prior', title: 'Keep 雪', done: true }];
|
||||
for (const [title, valid] of cases) {
|
||||
b.importBytes(encode(prior));
|
||||
const expected = [{ id: 'a', title, done: false }];
|
||||
const bytes = encode(expected), unchanged = Buffer.from(bytes);
|
||||
const result = attempt(b, bytes);
|
||||
assert.deepEqual(result, valid ? { rows: expected } : { error: 'schema' });
|
||||
assert.deepEqual(b.state(), valid ? expected : prior);
|
||||
assert.deepEqual(bytes, unchanged);
|
||||
}
|
||||
// 256 deterministic bounded sequences; inputs/expected values derived from
|
||||
// contract arithmetic, never either implementation's results. No RNG service.
|
||||
const alphabet = ['a', '雪', '😀', '\n', '\u0000', '\ud800', '\udfff', ' '];
|
||||
for (let seed = 0; seed < 256; seed++) {
|
||||
const length = seed % 83;
|
||||
const scalar = alphabet[Math.floor(seed / 32)];
|
||||
const title = scalar.repeat(length);
|
||||
const valid = length >= 1 && length <= 80 && scalar !== '\ud800' && scalar !== '\udfff';
|
||||
const row = { id: 'a', title, done: seed % 2 === 0 };
|
||||
const version = seed % 2 + 1;
|
||||
const bytes = encode([version === 1 ? { id: 'a', text: title } : row], version);
|
||||
a.importBytes(encode(prior)); b.importBytes(encode(prior));
|
||||
const expected = valid ? { rows: [{ ...row, done: version === 2 ? row.done : false }] } : { error: 'schema' };
|
||||
assert.deepEqual(attempt(a, bytes), expected);
|
||||
assert.deepEqual(attempt(b, bytes), expected);
|
||||
assert.deepEqual(b.state(), valid ? expected.rows : prior);
|
||||
assert.deepEqual(b.exportBytes(), a.exportBytes());
|
||||
generated++;
|
||||
}
|
||||
return { status: 'passed', frozenCasesPerTarget: historical.casesPerTarget,
|
||||
unicodeBoundaries: cases.length, generatedSequences: generated,
|
||||
persistenceFaultTargets: historical.faults.length,
|
||||
limits: 'Same-agent source-exposed finite tests; historical CLI unchanged; no exhaustive equivalence proof.' };
|
||||
}
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
console.log(JSON.stringify(check(), null, 2));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"frozenCasesPerTarget": 33,
|
||||
"unicodeBoundaries": 12,
|
||||
"generatedSequences": 256,
|
||||
"persistenceFaultTargets": 2,
|
||||
"limits": "Same-agent source-exposed finite tests; historical CLI unchanged; no exhaustive equivalence proof."
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
{
|
||||
"experiment": "MO-20261018",
|
||||
"phase": "final-1",
|
||||
"runtime": {
|
||||
"node": "v24.21.0",
|
||||
"v8": "13.6.233.17-node.53",
|
||||
"platform": "win32",
|
||||
"arch": "x64",
|
||||
"flags": [],
|
||||
"nodeOptions": ""
|
||||
},
|
||||
"identities": {
|
||||
"PROTOCOL.md": "7dfb4c64edc7822a89e5f4d4ec8990c83a3693ecff56d280bd27bdb27f612a86",
|
||||
"workloads.mjs": "7d50995f0cf811e134f74d04a97f051a092efa93a0372632d03d53e404911f6c",
|
||||
"bench.mjs": "f014a06ed2efaf34735738ca5b0b874c1311b261e98846ab1800dc1a10da3c54",
|
||||
"../import-export/candidate.mjs": "7b29d66619d7e44c958d83f9520354a05729105f1f444e6be9bea68a0cfe3f1e",
|
||||
"../import-export/reference.mjs": "9ca2b75875e22aee8f84644b9d1d520fa7da798efb8553e9311211648644e1ec",
|
||||
"../import-export/corpus.json": "624f497538556c74d81c8ce37fb42342a21dfb039693943652fe2c0d580382ef",
|
||||
"../import-export/identity.json": "5ba5a46b352a4df7470052cf6b9acefe0bb490d6d52da81a556f0ea43d849359",
|
||||
"candidate.mjs": "70dce76aed0eaee999c3181034e68e14bf344577db149f460384abb52b636b64"
|
||||
},
|
||||
"workloads": [
|
||||
{
|
||||
"id": "small",
|
||||
"iterations": 1000,
|
||||
"bytes": 89,
|
||||
"sha256": "f6dd4ca283d7a3b01b35b65ccfc85ec47580ebbd30c086ce46f4a734145f1c21",
|
||||
"scalarsPerTitle": 4
|
||||
},
|
||||
{
|
||||
"id": "typical",
|
||||
"iterations": 250,
|
||||
"bytes": 750,
|
||||
"sha256": "8e7bb8239f4b3a3d081a5b9b5ba017a55b332f1a3584691cf5aab3419984920f",
|
||||
"scalarsPerTitle": 32
|
||||
},
|
||||
{
|
||||
"id": "stress-ascii",
|
||||
"iterations": 100,
|
||||
"bytes": 1904,
|
||||
"sha256": "39a71f7fc41fa4803bfb66952338cb4ebdaf1774873ee382733b9d03200582c2",
|
||||
"scalarsPerTitle": 80
|
||||
},
|
||||
{
|
||||
"id": "stress-unicode",
|
||||
"iterations": 100,
|
||||
"bytes": 3824,
|
||||
"sha256": "ae1a187974a6b9f1fee3d49227235816bafa3b27d6fae83fd17e7edc0c764764",
|
||||
"scalarsPerTitle": 80
|
||||
}
|
||||
],
|
||||
"warmupBatches": 2,
|
||||
"pairs": 12,
|
||||
"samples": [
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.882400000000004,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.1547000000000054,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.0294000000000096,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6169999999999902,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.7043999999999926,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2313999999999936,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 4.381399999999999,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.762600000000006,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6225000000000023,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.8452000000000055,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.548400000000001,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.5365000000000038,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.503,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.6258000000000123,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9575000000000102,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.256699999999995,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1219999999999857,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.0755000000000052,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.405300000000011,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.458200000000005,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1683999999999912,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.163499999999999,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.5369999999999777,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.4976000000000056,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.7634000000000185,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.253900000000016,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.1913999999999874,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1794999999999902,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.821300000000008,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.754000000000019,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.1152999999999906,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.044799999999981,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.220300000000009,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.047799999999995,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.650299999999987,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.5594999999999857,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9144000000000005,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.4052999999999827,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.473000000000013,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.5146000000000015,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6262000000000114,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.4628000000000156,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2332999999999856,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9081000000000188,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.5684000000000253,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2729000000000212,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.061700000000002,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.278400000000005,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.3181000000000154,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.1598999999999933,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.338799999999992,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.4085999999999785,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0434000000000196,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9478000000000009,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.514499999999998,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.5197000000000003,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.379499999999979,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.438299999999998,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.671500000000009,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0161999999999978,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.8367000000000075,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.4259999999999877,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.026900000000012,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.50709999999998,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.102300000000014,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9873999999999796,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.775599999999997,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.515199999999993,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.129799999999989,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.327099999999973,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.892499999999984,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.856300000000033,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.4804999999999495,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.4997000000000185,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.3058000000000334,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0687000000000353,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.651999999999987,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.6415000000000077,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.173400000000015,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.037999999999954,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8687999999999647,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9026000000000067,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.205699999999979,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.2328999999999724,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.101799999999969,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.8403000000000134,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.3991000000000327,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.699800000000039,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.300200000000018,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.02800000000002,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9952000000000112,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.904800000000023,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6211000000000126,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.36160000000001,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9875999999999863,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8443000000000325,
|
||||
"checksum": 134200
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
{
|
||||
"experiment": "MO-20261018",
|
||||
"phase": "final-2",
|
||||
"runtime": {
|
||||
"node": "v24.21.0",
|
||||
"v8": "13.6.233.17-node.53",
|
||||
"platform": "win32",
|
||||
"arch": "x64",
|
||||
"flags": [],
|
||||
"nodeOptions": ""
|
||||
},
|
||||
"identities": {
|
||||
"PROTOCOL.md": "7dfb4c64edc7822a89e5f4d4ec8990c83a3693ecff56d280bd27bdb27f612a86",
|
||||
"workloads.mjs": "7d50995f0cf811e134f74d04a97f051a092efa93a0372632d03d53e404911f6c",
|
||||
"bench.mjs": "f014a06ed2efaf34735738ca5b0b874c1311b261e98846ab1800dc1a10da3c54",
|
||||
"../import-export/candidate.mjs": "7b29d66619d7e44c958d83f9520354a05729105f1f444e6be9bea68a0cfe3f1e",
|
||||
"../import-export/reference.mjs": "9ca2b75875e22aee8f84644b9d1d520fa7da798efb8553e9311211648644e1ec",
|
||||
"../import-export/corpus.json": "624f497538556c74d81c8ce37fb42342a21dfb039693943652fe2c0d580382ef",
|
||||
"../import-export/identity.json": "5ba5a46b352a4df7470052cf6b9acefe0bb490d6d52da81a556f0ea43d849359",
|
||||
"candidate.mjs": "70dce76aed0eaee999c3181034e68e14bf344577db149f460384abb52b636b64"
|
||||
},
|
||||
"workloads": [
|
||||
{
|
||||
"id": "small",
|
||||
"iterations": 1000,
|
||||
"bytes": 89,
|
||||
"sha256": "f6dd4ca283d7a3b01b35b65ccfc85ec47580ebbd30c086ce46f4a734145f1c21",
|
||||
"scalarsPerTitle": 4
|
||||
},
|
||||
{
|
||||
"id": "typical",
|
||||
"iterations": 250,
|
||||
"bytes": 750,
|
||||
"sha256": "8e7bb8239f4b3a3d081a5b9b5ba017a55b332f1a3584691cf5aab3419984920f",
|
||||
"scalarsPerTitle": 32
|
||||
},
|
||||
{
|
||||
"id": "stress-ascii",
|
||||
"iterations": 100,
|
||||
"bytes": 1904,
|
||||
"sha256": "39a71f7fc41fa4803bfb66952338cb4ebdaf1774873ee382733b9d03200582c2",
|
||||
"scalarsPerTitle": 80
|
||||
},
|
||||
{
|
||||
"id": "stress-unicode",
|
||||
"iterations": 100,
|
||||
"bytes": 3824,
|
||||
"sha256": "ae1a187974a6b9f1fee3d49227235816bafa3b27d6fae83fd17e7edc0c764764",
|
||||
"scalarsPerTitle": 80
|
||||
}
|
||||
],
|
||||
"warmupBatches": 2,
|
||||
"pairs": 12,
|
||||
"samples": [
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.196399999999997,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.8359999999999985,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6795000000000044,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.8708000000000027,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2151000000000067,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2192000000000007,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 4.366100000000003,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 0,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 4.744399999999999,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.906499999999994,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.9089000000000027,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.268799999999999,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.877600000000001,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.4539999999999793,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 4.5797000000000025,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2933000000000163,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 1,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.7034000000000162,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.193899999999985,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8871000000000038,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.681999999999988,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2430000000000234,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2753999999999905,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.2413999999999987,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.595400000000012,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 2,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.727400000000017,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.4285999999999888,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.6185000000000116,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.3917000000000144,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.295000000000016,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.997700000000009,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6873999999999967,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9577999999999918,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 3,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.118799999999993,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.1411999999999978,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0993999999999744,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6370999999999754,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.529800000000023,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.062100000000015,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1833000000000027,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.6551000000000045,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 4,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2539999999999907,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.489699999999999,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.6245999999999867,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.912099999999981,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.0527000000000157,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.3362000000000194,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.5323000000000206,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0870999999999924,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 5,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2032999999999845,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.501499999999993,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.0921000000000163,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 4.002100000000013,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.7712000000000216,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2497999999999934,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.074799999999982,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.7956000000000074,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 6,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.617500000000007,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.272299999999973,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.52819999999997,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9972999999999956,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.996499999999969,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.559699999999964,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.5604999999999905,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.186300000000017,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 7,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.421299999999974,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9786000000000286,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.2081999999999766,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.7070999999999685,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.399400000000014,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.2613000000000056,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.015299999999968,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.7835000000000036,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 8,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.202699999999993,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.442600000000027,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.643599999999992,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8846000000000345,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.4565000000000055,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.245999999999981,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.8466999999999985,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.1497999999999706,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 9,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.0843000000000416,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.001000000000033,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.8506000000000427,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.36869999999999,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.0973000000000184,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.9837999999999738,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9057999999999993,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.4628999999999905,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 10,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 2.529299999999978,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.240100000000041,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-unicode",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 3.258199999999988,
|
||||
"checksum": 166200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "small",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.9919999999999618,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "small",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 1.8975000000000364,
|
||||
"checksum": 8000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "typical",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 3.2502000000000066,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "typical",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.5529999999999973,
|
||||
"checksum": 79000
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "baseline",
|
||||
"elapsedMs": 1.888900000000035,
|
||||
"checksum": 134200
|
||||
},
|
||||
{
|
||||
"pair": 11,
|
||||
"workload": "stress-ascii",
|
||||
"variant": "candidate",
|
||||
"elapsedMs": 2.041500000000042,
|
||||
"checksum": 134200
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"phase": "exploratory-profile",
|
||||
"iterations": 50000,
|
||||
"checksum": 83100000,
|
||||
"sampledHits": {
|
||||
"baseline:decode": 534,
|
||||
"other/runtime/consumer": 268,
|
||||
"baseline:serialize": 206,
|
||||
"(garbage collector)": 44,
|
||||
"baseline:(anonymous)": 32,
|
||||
"baseline:importBytes": 7,
|
||||
"baseline:fields": 4,
|
||||
"baseline:state": 1
|
||||
},
|
||||
"limitation": "Sampled CPU attribution, not elapsed comparison, allocation volume or proof of causality."
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// One exploratory baseline profile; summary deliberately omits paths/host names.
|
||||
import inspector from 'node:inspector';
|
||||
import { createStore } from '../import-export/candidate.mjs';
|
||||
import { workloads, consume } from './workloads.mjs';
|
||||
const session = new inspector.Session();
|
||||
session.connect();
|
||||
const post = (method, params = {}) => new Promise((resolve, reject) => {
|
||||
session.post(method, params, (error, result) => error ? reject(error) : resolve(result));
|
||||
});
|
||||
try {
|
||||
await post('Profiler.enable');
|
||||
await post('Profiler.start');
|
||||
const store = createStore(), input = workloads().find(w => w.id === 'stress-unicode');
|
||||
let checksum = 0;
|
||||
for (let i = 0; i < 50000; i++) checksum += consume(store.importBytes(input.bytes));
|
||||
const { profile } = await post('Profiler.stop');
|
||||
const counts = new Map();
|
||||
for (const node of profile.nodes) {
|
||||
const fn = node.callFrame.functionName || '(anonymous)';
|
||||
// Only named functions from the known fixture, native frames and GC buckets.
|
||||
const bucket = node.callFrame.url.endsWith('/import-export/candidate.mjs') ? `baseline:${fn}`
|
||||
: fn === '(garbage collector)' ? fn : 'other/runtime/consumer';
|
||||
counts.set(bucket, (counts.get(bucket) ?? 0) + (node.hitCount ?? 0));
|
||||
}
|
||||
console.log(JSON.stringify({ phase: 'exploratory-profile', iterations: 50000, checksum,
|
||||
sampledHits: Object.fromEntries([...counts].sort((a, b) => b[1] - a[1])),
|
||||
limitation: 'Sampled CPU attribution, not elapsed comparison, allocation volume or proof of causality.' }, null, 2));
|
||||
} finally { session.disconnect(); }
|
||||
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"baseline": [
|
||||
{
|
||||
"workload": "small",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 2.0004000000000133,
|
||||
"medianUs": 2.1572000000000173,
|
||||
"maxUs": 2.4181000000000092,
|
||||
"minBatchMs": 2.0004000000000133
|
||||
},
|
||||
"pairedRatioMedian": null,
|
||||
"candidateFasterPairs": null
|
||||
},
|
||||
{
|
||||
"workload": "typical",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 9.481999999999971,
|
||||
"medianUs": 10.046400000000006,
|
||||
"maxUs": 13.678800000000026,
|
||||
"minBatchMs": 2.3704999999999927
|
||||
},
|
||||
"pairedRatioMedian": null,
|
||||
"candidateFasterPairs": null
|
||||
},
|
||||
{
|
||||
"workload": "stress-ascii",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 18.540000000000134,
|
||||
"medianUs": 20.424499999999952,
|
||||
"maxUs": 26.561000000000096,
|
||||
"minBatchMs": 1.8540000000000134
|
||||
},
|
||||
"pairedRatioMedian": null,
|
||||
"candidateFasterPairs": null
|
||||
},
|
||||
{
|
||||
"workload": "stress-unicode",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 30.93500000000006,
|
||||
"medianUs": 32.8875,
|
||||
"maxUs": 46.84400000000011,
|
||||
"minBatchMs": 3.093500000000006
|
||||
},
|
||||
"pairedRatioMedian": null,
|
||||
"candidateFasterPairs": null
|
||||
}
|
||||
],
|
||||
"final-1": [
|
||||
{
|
||||
"workload": "small",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 1.904800000000023,
|
||||
"medianUs": 2.1353500000000025,
|
||||
"maxUs": 2.882400000000004,
|
||||
"minBatchMs": 1.904800000000023
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 1.8403000000000134,
|
||||
"medianUs": 2.0547499999999985,
|
||||
"maxUs": 2.6715000000000093,
|
||||
"minBatchMs": 1.8403000000000134
|
||||
},
|
||||
"pairedRatioMedian": 0.9492804017073306,
|
||||
"candidateFasterPairs": 8
|
||||
},
|
||||
{
|
||||
"workload": "typical",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 9.44640000000004,
|
||||
"medianUs": 10.102999999999952,
|
||||
"maxUs": 12.117600000000039,
|
||||
"minBatchMs": 2.36160000000001
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 9.921999999999798,
|
||||
"medianUs": 10.48720000000003,
|
||||
"maxUs": 14.237999999999941,
|
||||
"minBatchMs": 2.4804999999999495
|
||||
},
|
||||
"pairedRatioMedian": 1.0132525939240118,
|
||||
"candidateFasterPairs": 5
|
||||
},
|
||||
{
|
||||
"workload": "stress-ascii",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 18.443000000000325,
|
||||
"medianUs": 20.953500000000105,
|
||||
"maxUs": 27.043999999999922,
|
||||
"minBatchMs": 1.8443000000000325
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 19.026000000000067,
|
||||
"medianUs": 21.956499999999934,
|
||||
"maxUs": 25.48400000000001,
|
||||
"minBatchMs": 1.9026000000000067
|
||||
},
|
||||
"pairedRatioMedian": 1.026282086066642,
|
||||
"candidateFasterPairs": 4
|
||||
},
|
||||
{
|
||||
"workload": "stress-unicode",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 30.2800000000002,
|
||||
"medianUs": 34.218000000000046,
|
||||
"maxUs": 43.81399999999999,
|
||||
"minBatchMs": 3.02800000000002
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 32.32899999999972,
|
||||
"medianUs": 35.08800000000001,
|
||||
"maxUs": 38.56300000000033,
|
||||
"minBatchMs": 3.2328999999999724
|
||||
},
|
||||
"pairedRatioMedian": 1.0102315015990466,
|
||||
"candidateFasterPairs": 4
|
||||
}
|
||||
],
|
||||
"final-2": [
|
||||
{
|
||||
"workload": "small",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 1.9057999999999993,
|
||||
"medianUs": 2.1245999999999725,
|
||||
"maxUs": 3.2933000000000168,
|
||||
"minBatchMs": 1.9057999999999993
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 1.8975000000000364,
|
||||
"medianUs": 2.1687999999999974,
|
||||
"maxUs": 2.703400000000016,
|
||||
"minBatchMs": 1.8975000000000364
|
||||
},
|
||||
"pairedRatioMedian": 0.984565754381505,
|
||||
"candidateFasterPairs": 7
|
||||
},
|
||||
{
|
||||
"workload": "typical",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 9.597600000000057,
|
||||
"medianUs": 10.354399999999941,
|
||||
"maxUs": 15.483200000000013,
|
||||
"minBatchMs": 2.399400000000014
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 9.851599999999962,
|
||||
"medianUs": 10.561399999999935,
|
||||
"maxUs": 15.63560000000001,
|
||||
"minBatchMs": 2.4628999999999905
|
||||
},
|
||||
"pairedRatioMedian": 1.0213634783688195,
|
||||
"candidateFasterPairs": 5
|
||||
},
|
||||
{
|
||||
"workload": "stress-ascii",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 18.506000000000427,
|
||||
"medianUs": 19.8654999999998,
|
||||
"maxUs": 32.19200000000001,
|
||||
"minBatchMs": 1.8506000000000427
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 20.010000000000332,
|
||||
"medianUs": 22.04499999999996,
|
||||
"maxUs": 28.776000000000014,
|
||||
"minBatchMs": 2.001000000000033
|
||||
},
|
||||
"pairedRatioMedian": 1.0948613489680883,
|
||||
"candidateFasterPairs": 2
|
||||
},
|
||||
{
|
||||
"workload": "stress-unicode",
|
||||
"baseline": {
|
||||
"count": 12,
|
||||
"minUs": 30.973000000000184,
|
||||
"medianUs": 32.63149999999982,
|
||||
"maxUs": 47.44399999999999,
|
||||
"minBatchMs": 3.0973000000000184
|
||||
},
|
||||
"candidate": {
|
||||
"count": 12,
|
||||
"minUs": 32.58199999999988,
|
||||
"medianUs": 36.68549999999996,
|
||||
"maxUs": 45.797000000000025,
|
||||
"minBatchMs": 3.258199999999988
|
||||
},
|
||||
"pairedRatioMedian": 1.082913290872317,
|
||||
"candidateFasterPairs": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { identities } from './bench.mjs';
|
||||
import { workloads, consume } from './workloads.mjs';
|
||||
|
||||
export const median = values => {
|
||||
assert.ok(values.length > 0);
|
||||
const a = [...values].sort((x, y) => x - y), mid = Math.floor(a.length / 2);
|
||||
return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) / 2;
|
||||
};
|
||||
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
||||
export function validate(record, phase) {
|
||||
assert.ok(['baseline', 'final-1', 'final-2'].includes(phase));
|
||||
assert.deepEqual(Object.keys(record).sort(), ['experiment', 'identities', 'pairs', 'phase', 'runtime', 'samples', 'warmupBatches', 'workloads']);
|
||||
assert.equal(record.experiment, 'MO-20261018'); assert.equal(record.phase, phase);
|
||||
assert.deepEqual(record.identities, identities(phase !== 'baseline'), 'source identity');
|
||||
assert.equal(record.warmupBatches, 2); assert.equal(record.pairs, 12);
|
||||
assert.deepEqual(Object.keys(record.runtime).sort(), ['arch', 'flags', 'node', 'nodeOptions', 'platform', 'v8']);
|
||||
for (const key of ['node', 'v8', 'platform', 'arch']) assert.equal(typeof record.runtime[key], 'string');
|
||||
assert.deepEqual(record.runtime.flags, []); assert.equal(record.runtime.nodeOptions, '');
|
||||
const inputs = workloads();
|
||||
assert.deepEqual(record.workloads, inputs.map(w => ({ id: w.id, iterations: w.iterations,
|
||||
bytes: w.bytes.length, sha256: hash(w.bytes), scalarsPerTitle: Array.from(w.expected[0].title).length })));
|
||||
const variants = phase === 'baseline' ? ['baseline'] : ['baseline', 'candidate'];
|
||||
assert.equal(record.samples.length, 12 * inputs.length * variants.length, 'complete samples');
|
||||
let index = 0;
|
||||
for (let pair = 0; pair < 12; pair++) for (let offset = 0; offset < inputs.length; offset++) {
|
||||
const w = inputs[(pair + offset) % inputs.length], order = [...variants];
|
||||
if ((pair + Number(phase === 'final-2')) % 2) order.reverse();
|
||||
for (const variant of order) {
|
||||
const sample = record.samples[index++];
|
||||
assert.deepEqual(Object.keys(sample).sort(), ['checksum', 'elapsedMs', 'pair', 'variant', 'workload']);
|
||||
assert.equal(sample.pair, pair); assert.equal(sample.workload, w.id); assert.equal(sample.variant, variant);
|
||||
assert.ok(Number.isFinite(sample.elapsedMs) && sample.elapsedMs > 0, 'finite positive elapsed time');
|
||||
assert.equal(sample.checksum, consume(w.expected) * w.iterations, 'complete output consumption');
|
||||
}
|
||||
}
|
||||
return record;
|
||||
}
|
||||
export function summarize(record) {
|
||||
validate(record, record.phase);
|
||||
const result = [];
|
||||
for (const w of record.workloads) {
|
||||
const variants = record.phase === 'baseline' ? ['baseline'] : ['baseline', 'candidate'];
|
||||
const statistics = {};
|
||||
for (const variant of variants) {
|
||||
const samples = record.samples.filter(s => s.workload === w.id && s.variant === variant);
|
||||
const us = samples.map(s => s.elapsedMs * 1000 / w.iterations);
|
||||
statistics[variant] = { count: us.length, minUs: Math.min(...us), medianUs: median(us), maxUs: Math.max(...us),
|
||||
minBatchMs: Math.min(...samples.map(s => s.elapsedMs)) };
|
||||
}
|
||||
const ratios = variants.length === 2 ? Array.from({ length: 12 }, (_, pair) => {
|
||||
const samples = record.samples.filter(s => s.workload === w.id && s.pair === pair);
|
||||
return samples.find(s => s.variant === 'candidate').elapsedMs / samples.find(s => s.variant === 'baseline').elapsedMs;
|
||||
}) : null;
|
||||
result.push({ workload: w.id, ...statistics, pairedRatioMedian: ratios && median(ratios),
|
||||
candidateFasterPairs: ratios && ratios.filter(r => r < 1).length });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const root = path.dirname(fileURLToPath(import.meta.url));
|
||||
console.log(JSON.stringify(Object.fromEntries(['baseline', 'final-1', 'final-2'].map(phase =>
|
||||
[phase, summarize(JSON.parse(fs.readFileSync(path.join(root, `${phase}.json`))))])), null, 2));
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export const encode = (entries, version = 2) => Buffer.from(JSON.stringify({
|
||||
format: 'pocket-ledger', version, entries
|
||||
}));
|
||||
const rows = (count, title) => Array.from({ length: count }, (_, i) => ({
|
||||
id: `r${i}`, title, done: i % 2 === 0
|
||||
}));
|
||||
export function workloads() {
|
||||
return [
|
||||
['small', rows(1, 'Task'), 1000],
|
||||
['typical', rows(8, 'Task 雪😀!'.repeat(4)), 250],
|
||||
['stress-ascii', rows(16, 'abcdefgh'.repeat(10)), 100],
|
||||
['stress-unicode', rows(16, '雪😀a\n'.repeat(20)), 100],
|
||||
].map(([id, expected, iterations]) => ({ id, expected, iterations, bytes: encode(expected) }));
|
||||
}
|
||||
// Consume each returned field equally, without adding a second serialization.
|
||||
export function consume(rows) {
|
||||
let sum = rows.length;
|
||||
for (const row of rows) sum += row.id.length + row.title.length + Number(row.done);
|
||||
return sum;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# Offline multi-selection and bulk-action example
|
||||
|
||||
Run `node --test tests/multi-selection.test.mjs` from the repository root.
|
||||
Existing Node.js is sufficient; no dependency, model call, network, OS clipboard
|
||||
or real user data is involved. `double.mjs` records synthetic synchronous batch
|
||||
attempts independently of candidate snapshots.
|
||||
|
||||
One proposed teaching profile covers three families: scope/identity, atomic
|
||||
versus best-effort outcomes, and retry/undo. The same author separately reasoned
|
||||
and froze 16 sequences / 70 literal intermediate states and attempt lists before
|
||||
candidate implementation. `identity.json` binds `expectations.json`; the checker
|
||||
does not calculate expected outputs from the candidate. A preliminary oracle
|
||||
draft was retained privately and corrected before implementation to preserve
|
||||
earlier refusals/unknown outcomes after retry. Source is exposed and authorship
|
||||
is shared; this is not independent evidence or a model-following benchmark.
|
||||
|
||||
## Finite proposed contract
|
||||
|
||||
Rows are `[stable ID, group, value, revision]` in one in-memory owner. IDs are
|
||||
one to eight lowercase letters; groups are `x`/`y`, values integers 0–99 and
|
||||
revisions positive safe integers. The operation increments a value once.
|
||||
Selection is an explicit unique ID set, reported in ascending ID order. It
|
||||
persists across view changes and removal; missing selections stay visible in
|
||||
the selection/report rather than silently becoming another row. Removed IDs
|
||||
cannot be reused during this fixture's lifetime. Production ID reuse/owner
|
||||
generations need their own evidence and policy.
|
||||
|
||||
The view filters by group, orders IDs ascending/descending and pages by two.
|
||||
Selected, page and all-filtered scope differ; filtered includes unseen pages.
|
||||
Preview captures exact membership, values/revisions, mode and undo grouping.
|
||||
Later view/selection changes or matching additions cannot broaden it. Empty
|
||||
scope creates no preview; cancel clears it; confirmation consumes it before
|
||||
dispatch, including error paths. A new preview replaces retry intent. These
|
||||
snapshot choices illustrate a policy, not universal select-all behavior.
|
||||
|
||||
Confirmation checks missing/stale members before dispatch, including value and
|
||||
revision changes and the finite numeric ceiling. Best effort skips those
|
||||
members and applies only acknowledged successes from the double. Refusal,
|
||||
retryable failure and unknown acknowledgement leave local values unchanged.
|
||||
Atomic mode prevalidates the entire set: an invalid member skips otherwise
|
||||
valid members without calling the double. With valid members, all must receive
|
||||
synthetic success before any local write; otherwise acknowledged successes are
|
||||
reported `rolled-back` and no local value/history changes. The double is not a
|
||||
real remote transaction: `rolled-back` describes discarded local proposals,
|
||||
and `retained` describes local data only. Unknown acknowledgements simulate a
|
||||
reporting boundary; no actual response loss, external write or reconciliation
|
||||
protocol is implemented or proven.
|
||||
|
||||
The report keeps every original member's latest outcome across retry. Retry
|
||||
dispatches only best-effort `retryable` members from the preceding attempt,
|
||||
using their original value/revision. Known successes, refusals, unknowns and
|
||||
new matches are excluded. Changed/missing retries are reported without an
|
||||
attempt. Atomic failures require a fresh preview rather than partial retry.
|
||||
Each retry is explicitly dispatched by the caller; there is no automatic retry
|
||||
loop. Repeated confirmation or retry with no eligible intent has no effects.
|
||||
|
||||
Successful local changes create one grouped history entry or one entry per
|
||||
successful item in application order. Undo checks the complete top entry
|
||||
before changing anything and restores values with the same IDs/order; revision
|
||||
counters advance rather than rewind. Changed/missing members reject the whole
|
||||
entry without losing it. Refused/failed members have no history entry. Selection,
|
||||
view, the historical batch report and recorded attempts survive undo. There is
|
||||
no redo, history persistence or general edit-history engine. Strict revision
|
||||
checks can leave older entries inapplicable even after values are restored;
|
||||
that conservative limit is intentional. Local undo cannot reverse external
|
||||
effects or resolve an unknown acknowledgement.
|
||||
|
||||
## Checks and limits
|
||||
|
||||
Seventeen disposable faults each pass six ordinary selected/atomic checkpoints
|
||||
before a specific state/attempt assertion rejects them: hidden selection,
|
||||
page/filtered confusion, position identity, live selection/membership, stale
|
||||
targets, partial atomic commit, retries of successes/unknowns/stale targets,
|
||||
lost earlier outcomes, wrong grouping, recreated identity, undo conflicts,
|
||||
effects on refusal and effects on cancel. Exceptions are not scored as useful
|
||||
detections. Further checks cover atomic prevalidation, partial/grouped/per-item
|
||||
undo, changed/removed retry members, revision restoration, ID reuse, finite
|
||||
validation, detached state, adapter error propagation and comparator sensitivity
|
||||
including independently observed concealed attempts.
|
||||
|
||||
No original app, real UI/backend, permissions, distributed atomicity,
|
||||
idempotency service, concurrency, crash durability, permanent deletion or
|
||||
external undo is tested. Adapter exceptions propagate and consume the preview;
|
||||
they do not prove a real effect failed or is safe to replay. Use the
|
||||
[focused guide](../../skills/rea-reconstruction-companion/references/multi-selection.md)
|
||||
to recover the actual target's behavior. Standalone installations omit these
|
||||
repository-only evaluation files. Existing historical examples are unchanged.
|
||||
@@ -0,0 +1,109 @@
|
||||
// One finite proposed local contract, not a production batch/transaction API.
|
||||
const equal = (a,b) => JSON.stringify(a) === JSON.stringify(b);
|
||||
const validRow = row => Array.isArray(row) && row.length === 4 &&
|
||||
typeof row[0] === 'string' && /^[a-z]{1,8}$/.test(row[0]) && ['x','y'].includes(row[1]) &&
|
||||
Number.isSafeInteger(row[2]) && row[2] >= 0 && row[2] < 100 && Number.isSafeInteger(row[3]) && row[3] > 0;
|
||||
export function createBatch(initial, service) {
|
||||
if (!Array.isArray(initial) || !initial.every(validRow) || new Set(initial.map(row => row[0])).size !== initial.length) throw Error('Invalid fixture');
|
||||
let rows = structuredClone(initial), selection = [], view = {filter:null,page:0,order:'asc'};
|
||||
const usedIds = new Set(rows.map(row => row[0]));
|
||||
let preview = null, report = [], retry = null, history = [], status = 'ok';
|
||||
const find = id => rows.find(row => row[0] === id);
|
||||
function filtered() {
|
||||
const ids = rows.filter(row => view.filter === null || row[1] === view.filter).map(row => row[0]).sort();
|
||||
return view.order === 'desc' ? ids.reverse() : ids;
|
||||
}
|
||||
const visible = () => filtered().slice(view.page*2,view.page*2+2);
|
||||
function scopeIds(scope) {
|
||||
const ids = scope === 'selected' ? selection : scope === 'page' ? visible() : scope === 'filtered' ? filtered() : null;
|
||||
return ids && [...ids].sort();
|
||||
}
|
||||
function snapshot() {
|
||||
return structuredClone({rows,selection,view:{...view,visible:visible()},preview,report,history:history.length,status});
|
||||
}
|
||||
function run(plan, isRetry) {
|
||||
const results = plan.targets.map(([id,value,revision]) => {
|
||||
const row = find(id);
|
||||
return [id,!row ? 'missing' : row[3] !== revision || row[2] !== value || row[2] >= 99 || row[3] >= Number.MAX_SAFE_INTEGER-1 ? 'stale' : 'ready'];
|
||||
});
|
||||
const eligible = results.filter(row => row[1] === 'ready');
|
||||
// Atomic local prevalidation refuses dispatch when any target is inapplicable.
|
||||
if (plan.mode === 'atomic' && eligible.length !== results.length) {
|
||||
for (const result of results) if (result[1] === 'ready') result[1] = 'skipped';
|
||||
} else if (eligible.length) {
|
||||
const outcomes = service.attempt(eligible.map(row => row[0]),plan.mode);
|
||||
if (!Array.isArray(outcomes) || outcomes.length !== eligible.length || outcomes.some(value => !['success','retryable','refused','unknown'].includes(value))) throw Error('Invalid service result');
|
||||
eligible.forEach((row,i) => { row[1] = outcomes[i]; });
|
||||
}
|
||||
const commit = plan.mode !== 'atomic' || results.every(row => row[1] === 'success');
|
||||
if (!commit) for (const result of results) if (result[1] === 'success') result[1] = 'rolled-back';
|
||||
const entries = [];
|
||||
for (const [id,outcome] of results) if (outcome === 'success') {
|
||||
const row = find(id), before = [...row];
|
||||
row[2] += 1; row[3] += 1;
|
||||
entries.push({before,after:[...row]});
|
||||
}
|
||||
if (entries.length) {
|
||||
if (plan.undo === 'grouped') history.push(entries);
|
||||
else history.push(...entries.map(entry => [entry]));
|
||||
}
|
||||
report = isRetry ? report.map(prior => results.find(row => row[0] === prior[0]) ?? prior) : results;
|
||||
retry = plan.mode === 'best-effort' ? {...plan,targets:plan.targets.filter(([id]) => results.some(row => row[0] === id && row[1] === 'retryable'))} : null;
|
||||
status = report.every(row => row[1] === 'success') ? 'complete' : report.some(row => row[1] === 'success') ? 'partial' : 'retained';
|
||||
}
|
||||
function dispatch(action) {
|
||||
status = 'ok';
|
||||
switch (action.type) {
|
||||
case 'view':
|
||||
if (![null,'x','y'].includes(action.filter) || !Number.isSafeInteger(action.page) || action.page < 0 || !['asc','desc'].includes(action.order)) { status = 'rejected'; break; }
|
||||
view = {filter:action.filter,page:action.page,order:action.order}; break;
|
||||
case 'select': {
|
||||
const ids = action.scope === 'ids' ? action.ids : scopeIds(action.scope);
|
||||
if (!Array.isArray(ids) || new Set(ids).size !== ids.length || ids.some(id => !find(id))) { status = 'rejected'; break; }
|
||||
selection = [...ids].sort(); break;
|
||||
}
|
||||
case 'preview': {
|
||||
preview = null; retry = null;
|
||||
const ids = scopeIds(action.scope);
|
||||
if (!ids || !['atomic','best-effort'].includes(action.mode) || !['grouped','per-item'].includes(action.undo)) { status = 'rejected'; break; }
|
||||
report = [];
|
||||
if (!ids.length) { status = 'empty'; break; }
|
||||
preview = {scope:action.scope,mode:action.mode,undo:action.undo,targets:ids.map(id => { const row = find(id); return [id,row?.[2] ?? null,row?.[3] ?? null]; })};
|
||||
status = 'preview'; break;
|
||||
}
|
||||
case 'cancel': preview = null; retry = null; status = 'cancelled'; break;
|
||||
case 'confirm': {
|
||||
if (!preview) { status = 'no-preview'; break; }
|
||||
const plan = preview; preview = null; run(plan,false); break;
|
||||
}
|
||||
case 'retry': {
|
||||
if (!retry?.targets.length) { status = 'no-retry'; break; }
|
||||
const plan = retry; retry = null; run(plan,true); break;
|
||||
}
|
||||
case 'edit': {
|
||||
const row = find(action.id);
|
||||
if (!row || !Number.isSafeInteger(action.value) || action.value < 0 || action.value >= 100 || row[3] >= Number.MAX_SAFE_INTEGER-1) { status = 'rejected'; break; }
|
||||
row[2] = action.value; row[3] += 1; break;
|
||||
}
|
||||
case 'add':
|
||||
if (!validRow(action.row) || usedIds.has(action.row[0])) { status = 'rejected'; break; }
|
||||
rows.push(structuredClone(action.row)); usedIds.add(action.row[0]); break;
|
||||
case 'remove':
|
||||
if (!find(action.id)) { status = 'rejected'; break; }
|
||||
rows = rows.filter(row => row[0] !== action.id); break;
|
||||
case 'undo': {
|
||||
const entry = history.at(-1);
|
||||
if (!entry) { status = 'empty-history'; break; }
|
||||
if (entry.some(item => !equal(find(item.after[0]),item.after) || item.after[3] >= Number.MAX_SAFE_INTEGER-1)) { status = 'undo-conflict'; break; }
|
||||
for (const item of entry) {
|
||||
const row = find(item.after[0]);
|
||||
row[2] = item.before[2]; row[3] += 1;
|
||||
}
|
||||
history.pop(); status = 'undone'; break;
|
||||
}
|
||||
default: throw Error('Unsupported fixture action');
|
||||
}
|
||||
return snapshot();
|
||||
}
|
||||
return {snapshot,dispatch};
|
||||
}
|
||||
Loaded 100 of 294 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user