Skip to content

feat(spec): let a provider declare the inputs a compile read - #106

Merged
e54-bot merged 3 commits into
mainfrom
claude/gh-105-compile-inputs-95e1f3
Oct 28, 2026
Merged

e54-bot merged 3 commits into
mainfrom
claude/gh-105-compile-inputs-95e1f3

Conversation

@e54-bot

@e54-bot e54-bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Summary

LPP 1.7 adds the compileInputs capability. A provider that advertises it returns compileInputs on every entry-based lpp/compile result: a sorted, duplicate-free array of absolute file: URIs. The array lists every file whose content the compile read and every directory whose listing it read, including non-source files (for example compile-time scripts) and probed-but-missing paths. A change to a path that is neither listed nor an entry of a listed directory cannot change the result.

The field sits outside the opaque artifact. It is gated like sourceIdentity (LPP 1.3): request "1.7" and require compileInputs: true. Document-supplied results and pre-1.7 sessions never carry it.

Field shape: trade-offs (also in ADR 0007)

Option Pro Con Decision
Complete input URI list, including directory listings Provider-neutral; client never reads the artifact; covers files added to a scanned directory Provider must track every read, including listings and failed probes Chosen
Completeness flag for the artifact files table Smallest OPY change Client must parse the opaque artifact; useless for providers without a table Rejected
URI list of files only Simpler to produce False guarantee for directory discovery: a new file changes the result Rejected
Per-input content digests Client skips one hash pass Client must hash later anyway; adds a hash/ordering model with no consumer need Rejected

Changes

  • Spec: §7.3 capability table, §7.4 and §19.2 negotiation, §10.2 field rules, §19.1 version list, §20 conformance surface; protocol versions now 1.0–1.7.
  • ADR 0007 and registry row.
  • Mock provider: negotiates 1.7, advertises compileInputs, returns the project directory and loaded .xdl files.
  • Fixtures: 69-compile-inputs (advertised; a non-source notes.txt is not an input) and 70-compile-inputs-unavailable (--without compileInputs, field omitted); sessions/1.7.json.
  • Runner: new invariants module checks every actual compile response, independent of the fixture. It rejects the field without the negotiated capability, on document-supplied results, or with a malformed shape, and rejects an entry result that omits it after negotiation. Unit tests cover reject and accept; a session test proves a provider fails even when the fixture expects the field.

Verification

  • cargo fmt --all --check, cargo clippy --all-targets -- -D warnings: clean.
  • cargo test --workspace: all pass.
  • lpp-conformance-runner --validate-only: 70 passed.
  • lpp-conformance-runner --provider lpp-mock-provider: 70 passed.
  • git diff --check: clean.

Not covered: the OPY provider (wrightkit/opy-rs#659) and the Wright consumer (wrightkit/wright#1005) are separate Issues.

Fixes #105

LPP 1.7 adds the independently negotiated compileInputs capability. An entry-based lpp/compile result then lists every file and directory listing the compile read, as sorted absolute file URIs, outside the opaque artifact. The reference mock provider implements it, fixtures 69-70 cover the advertised and unadvertised sessions, and the runner rejects the field without the negotiated capability. ADR 0007 records the field shape and the rejected alternatives.

Fixes #105
@e54-bot
e54-bot requested a review from Teakowa as a code owner October 28, 2026 06:28
@e54-bot

e54-bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Review verdict: CHANGES REQUESTED

Reviewed head b3472dd. Gates pass locally: fmt, clippy -D warnings, cargo test --workspace, --validate-only (70 passed), runner against the mock (70 passed), git diff --check. CI is green. Runner mutations are caught: removing the !negotiated rejection fails rejects_inputs_without_the_capability and compile_inputs_without_capability_fails_even_when_expected; changing the version gate to minor >= 0 fails rejects_inputs_without_the_capability; dropping the directory URI from the mock's inputs fails fixture 69. Protocol version 1.7 is not reused: main lists protocol versions 1.0–1.6. The package tags v1.7.x are a separate number series, and the protocol and package numbers were already offset before this PR.

1. The normative guarantee excludes entries of listed directories, so a client cannot ignore edits to files the compile never read

docs/spec/analysis-and-compilation.md §10.2, compileInputs bullet: "creating, editing, or deleting a path that is neither listed nor an entry of a listed directory cannot change the result."

Under this wording, every entry of a listed directory has no guarantee for any kind of change, edits included. Fixture 69 shows the problem. The mock lists ${PROJECT_URI}, so notes.txt is an entry of a listed directory. The spec then gives no guarantee that an edit to notes.txt leaves the result unchanged, but the fixture description calls it "not an input". In practice a provider lists the project root for every entry compile. So under the literal text, the wright freshness check (loaded_fingerprint, wright#1005) must treat an edit to any file in the root as a possible change. That gives no improvement over whole-tree hashing. ADR 0007 states the intended rule ("The client still hashes the listed files and listings itself"), but the ADR is not normative, and opy-rs#659 implements against the spec text.

The spec also does not define what a client must compare for a listed directory. The mock's result depends on more than the entry names. project.rs skips a *.xdl entry when file_type().is_dir(), so replacing directory x.xdl with file x.xdl changes the result while the set of names stays the same.

Required correction: state the guarantee as a function of the declared inputs. For example: "The result depends only on the content of each listed path that is a file, the existence of each listed path, and, for each listed directory, the names and kinds (file or directory) of its entries. A change that leaves all of these unchanged cannot change the result." Make the ADR consequence match this text.

Rule: issue #105 acceptance criterion "the spec defines the capability and field" and its desired behavior ("an edit to any file outside the set cannot change the result"). No WrightKit policy rule covers this.

2. The mock declares canonical paths, so retargeting a symlink changes the result without changing any declared input

conformance/mock-provider/src/project.rs, Loader::run. Each source path is canonicalized before it is keyed, and inputs is built from documents.keys(). Consider a project where support.xdl is a symlink to ../lib/a.xdl. The mock declares .../lib/a.xdl and the root directory. Retarget the link to ../lib/b.xdl: the result changes. No declared file content changes, and the root's entry names and kinds do not change either. Reproduced against the mock at b3472dd (--protocol-version 1.7, directory target, support.xdl -> ../lib/a.xdl, and lib/b.xdl with target = 9). The first compile declares [lib/a.xdl, proj, proj/entry.xdl] with no diagnostics. After ln -sfn ../lib/b.xdl proj/support.xdl, none of those three inputs has changed, but the next compile reports 1 diagnostic. The reference provider therefore breaks the guarantee, and the spec does not say whether URIs are the paths as traversed or the resolved paths.

Required correction: in §10.2, require that every path the provider traversed, including each symlink it followed, is listed. In the mock, include the pre-canonical path next to the canonical one. A fixture is not needed if the runner cannot create symlinks portably. The spec text and the mock must agree.

Rule: no WrightKit policy rule covers this. It breaks the PR's own normative guarantee in the reference implementation (repo AGENTS.md: "the mock is the reference implementation of the conformance suite").

3. Method table entry for lpp/compile lost its conjunction

docs/spec/conformance.md, lpp/compile row, required capability column. Old text: "projectLoading for an LPP 1.1+ entry request and sourceIdentity for an LPP 1.3+ entry result". New text: "...entry request sourceIdentity for an LPP 1.3+ entry result, and compileInputs ...". The "and" was removed and no comma was added. The row now reads as one clause. Required correction: "projectLoading for an LPP 1.1+ entry request, sourceIdentity for an LPP 1.3+ entry result, and compileInputs for an LPP 1.7+ entry result".

Rule: docs/documentation.md (normative contract text is kept accurate).

State the compileInputs guarantee as a function of the listed paths: existence and kind, symlink targets, file content, and directory entry names and kinds. An edit to an unlisted file inside a listed directory no longer counts as a possible change.

Require every followed symbolic link to be listed with its resolved path. The mock now declares traversed paths before resolution, and a stdio test covers a symlinked source. Restore the list punctuation in the conformance method table.

Refs #105
@e54-bot

e54-bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Review fixes pushed in cbb41e9 (no force push). All three findings from #106 (comment) are addressed.

1. Guarantee stated as a function of the declared inputs

  • docs/spec/analysis-and-compilation.md §10.2: the result now depends only on these observations: existence and kind (file, directory, or symbolic link, without following a final link) of each listed path; the target of each listed link; the content of each listed file; and the entry names and kinds of each listed directory (without following links). The spec states that a client compares these observations. It also says that editing an unlisted file is a no-op even when that file is an entry of a listed directory.
  • Directory kind covers the case you raised: a x.xdl directory replaced by a x.xdl file changes an entry kind of the listed root.
  • ADR 0007: the Decision and Consequences sections match this rule.
  • Fixture 69: the description now explains why notes.txt is not listed and why editing it cannot change the result. The expected response is unchanged.

2. Followed symbolic links are declared

  • Spec §10.2: inputs are listed in the form they were accessed, before resolution. The list includes every symbolic link the provider followed, including a link among the ancestors of an accessed path, and also the link's resolved path.
  • Mock project.rs: it records the traversed paths (the entry as given, the default entry.xdl, the root, and each directory entry before canonicalize). It also adds every symbolic link among their ancestors.
  • New test compile_inputs_declare_followed_symlinks (conformance/mock-provider/tests/stdio.rs): proj/support.xdl -> ../lib/a.xdl, and the test asserts that both the link path and lib/a.xdl are declared. Mutation check: removing the pre-resolution paths from the mock makes the test fail with .../proj/support.xdl missing from [.../lib/a.xdl, .../proj, .../proj/entry.xdl].
  • ADR 0007: "Canonical paths only" is now listed as a rejected alternative.

3. Method table wording

  • docs/spec/conformance.md, lpp/compile row: "projectLoading for an LPP 1.1+ entry request, sourceIdentity for an LPP 1.3+ entry result, and compileInputs for an LPP 1.7+ entry result".

Gates at cbb41e9

  • cargo fmt --all --check, cargo clippy --all-targets -- -D warnings: clean.
  • cargo test --workspace: all pass (mock stdio tests: 6 passed).
  • --validate-only: 70 passed. Runner against the mock: 70 passed.
  • git diff --check: clean.

Please run a follow-up review.

@e54-bot

e54-bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Review verdict: CHANGES REQUESTED

Follow-up review at cbb41e9. Findings 1 and 3 are fixed. Finding 2 is fixed in the spec and for a single link, but the mock still breaks the rule for links that the provider follows while it resolves a link target.

  • Finding 1, fixed. §10.2 now defines the result as a function of four observations: existence and kind, link target, file content, and directory entry names and kinds. It states that editing an unlisted entry of a listed directory has no effect. ADR 0007 and the fixture 69 description match the spec.
  • Finding 3, fixed. The lpp/compile row in conformance.md now has the correct wording.
  • Gates at cbb41e9: fmt, clippy -D warnings, cargo test --workspace (mock stdio: 6 passed), --validate-only 70 passed, runner against the mock 70 passed, git diff --check clean. CI is green.
  • The single-link repro now declares proj/support.xdl and detects the retarget. Mutation: filtering symlinks out of the declared paths makes compile_inputs_declare_followed_symlinks fail with .../proj/support.xdl missing from [...].

2 (still open). The mock declares only the first link and the canonical path, not every link it followed

§10.2 now requires "every symbolic link the provider followed". declared_paths in conformance/mock-provider/src/project.rs adds only two kinds of link: a traversed path that is a link, and a link among the ancestors of the path as written. canonicalize also follows the links inside each link's target. The mock does not declare those links.

Reproduced against the mock at cbb41e9 (--protocol-version 1.7, directory target proj):

  • Link chain: proj/support.xdl -> ../lib/mid.xdl and lib/mid.xdl -> a.xdl. The mock declares [lib/a.xdl, proj, proj/entry.xdl, proj/support.xdl] and the compile reports 0 diagnostics. Run ln -sfn b.xdl lib/mid.xdl. All four declared observations stay the same, but the next compile reports 1 diagnostic.
  • Link through a linked directory: proj/support.xdl -> ../ld/a.xdl and ld -> lib. The mock declares the same four paths and the compile reports 0 diagnostics. Run ln -sfn lib2 ld (lib2/a.xdl contains target = 9). The declared observations stay the same, but the next compile reports 1 diagnostic.

So the reference provider still lets a result change while no declared input changes. This is the guarantee from finding 2.

Required correction: resolve each traversed path one component at a time and declare every link encountered, including links reached through a link's target. Alternatively, declare read_link for each hop and recurse into each hop's ancestors. Extend compile_inputs_declare_followed_symlinks with a two-hop chain so a mutation that declares only the first link fails.

Rule: no WrightKit policy rule covers this. It is the PR's own §10.2 rule, and the repo AGENTS.md makes the mock the reference implementation.

Resolve each traversed path component by component and record every symbolic link on the way, including links in a chain and linked directory components. A stdio test covers a two-link chain and a linked directory.

Refs #105
@e54-bot

e54-bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Round-2 fix pushed in f0159e8 (no force push). It addresses the remaining item from #106 (comment). Only the mock and its tests changed.

Intermediate links are declared

  • conformance/mock-provider/src/project.rs: declared_paths now resolves each traversed path one component at a time (new resolve helper, 40-hop limit). It records every symbolic link it passes through. A link target is resolved recursively, so the record includes each link in a chain and each linked directory component.
  • New test compile_inputs_declare_links_followed_during_resolution (conformance/mock-provider/tests/stdio.rs) covers both repros:
    • (a) chain: proj/chain.xdl -> ../lib/mid.xdl -> a.xdl. The test asserts that proj/chain.xdl and lib/mid.xdl are declared.
    • (b) linked directory component: proj/linked-dir.xdl -> ../ld/c.xdl with ld -> lib. The test asserts that proj/linked-dir.xdl and ld are declared.
  • Mutation check: with project.rs at cbb41e9, the test fails. The previous mock declares only ["lib/a.xdl","lib/c.xdl","proj","proj/chain.xdl","proj/entry.xdl","proj/linked-dir.xdl"], so both lib/mid.xdl and ld are missing.

Gates at f0159e8

  • cargo fmt --all --check, cargo clippy --all-targets -- -D warnings: clean.
  • cargo test --workspace: all pass (mock stdio tests: 7 passed).
  • --validate-only: 70 passed. Runner against the mock: 70 passed.
  • git diff --check: clean.

Please run a follow-up review.

@e54-bot

e54-bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Review verdict: LGTM

Follow-up at f0159e8. Finding 2 is fixed. The commit changes only conformance/mock-provider/src/project.rs and conformance/mock-provider/tests/stdio.rs.

  • Two-link chain: proj/support.xdl -> ../lib/mid.xdl -> a.xdl. The mock now declares lib/mid.xdl. After ln -sfn b.xdl lib/mid.xdl, that declared link's target changes, and the result changes from 0 diagnostics to 1.
  • Linked directory: proj/support.xdl -> ../ld/a.xdl with ld -> lib. The mock now declares ld. After ln -sfn lib2 ld, that declared link's target changes, and the result changes from 0 diagnostics to 1.
  • Mutation: when the mock does not recurse into link targets, compile_inputs_declare_links_followed_during_resolution fails with .../lib/mid.xdl missing from [...]. I reverted the mutation.
  • Gates: fmt, clippy -D warnings, cargo test --workspace (mock stdio: 7 passed), --validate-only 70 passed, runner against the mock 70 passed, git diff --check clean. CI is green.

@e54-bot
e54-bot merged commit f3355bf into main Oct 28, 2026
2 checks passed
@e54-bot
e54-bot deleted the claude/gh-105-compile-inputs-95e1f3 branch October 28, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let a provider declare the complete set of inputs a compile read

2 participants