From 6696e2b7b14ce9743d587d1409584f4d39ff0c84 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 24 Aug 2026 07:21:36 -0700 Subject: [PATCH 01/10] Add curated Edict Rust facade --- ARCHITECTURE.md | 15 +++++- CHANGELOG.md | 7 +++ Cargo.lock | 7 +++ Cargo.toml | 1 + README.md | 13 ++++-- crates/edict/Cargo.toml | 20 ++++++++ crates/edict/README.md | 24 ++++++++++ crates/edict/src/lib.rs | 46 +++++++++++++++++++ crates/edict/tests/public_surface.rs | 48 ++++++++++++++++++++ docs/topics/README.md | 3 ++ docs/topics/public-rust-api/README.md | 16 +++++++ docs/topics/public-rust-api/test-plan.md | 44 ++++++++++++++++++ fixtures/providers/components/inventory.json | 2 +- 13 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 crates/edict/Cargo.toml create mode 100644 crates/edict/README.md create mode 100644 crates/edict/src/lib.rs create mode 100644 crates/edict/tests/public_surface.rs create mode 100644 docs/topics/public-rust-api/README.md create mode 100644 docs/topics/public-rust-api/test-plan.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6a6c6d7b..38ba435b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,9 +5,10 @@ current branch; it is not a future package plan. ## Workspace Shape -The Rust workspace has five members: +The Rust workspace has six members: ```text +flyingrobots-edict -> edict-syntax edict-cli -> edict-syntax xtask -> edict-syntax edict-provider-schema -> edict-syntax @@ -26,6 +27,18 @@ runtime without exposing Wasmtime types through Edict contracts. ## Crates +### `flyingrobots-edict` + +`flyingrobots-edict` exposes the Rust library name `edict`. It is the curated +public facade for source checking, stable diagnostic classifications, and +canonical Core, Target IR, and result-projection artifact identity operations. +It deliberately does not re-export the implementation crate's module tree. + +The package remains `publish = false`. Its presence defines and tests the +intended public Rust boundary; it neither authorizes nor claims crates.io +publication. The CLI remains the complete application-build and JSONL process +boundary. + ### `edict-syntax` `edict-syntax` is the implementation crate for more than syntax. Its public name diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf1a3ee..d5d80016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,13 @@ versions still track specification maturity rather than a released product. ### Added +- Added the non-publishing `flyingrobots-edict` package with the Rust library + name `edict`. The curated facade exposes source checking, stable diagnostic + classifications, and canonical Core, Target IR, and result-projection + artifact identity operations without making the implementation crate's + module tree part of the recommended public API. The package remains + `publish = false`; registry naming, publication, and release authorization + are explicitly outside this change. - Added a public application-owned lawpack authoring boundary. One bounded `edict.lawpack-build/v1` review document now emits deterministic canonical manifests, exports, adapters, local resources, and digest sidecars through diff --git a/Cargo.lock b/Cargo.lock index 380650ab..ae838fec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -504,6 +504,13 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" +[[package]] +name = "flyingrobots-edict" +version = "0.11.0-alpha.1" +dependencies = [ + "edict-syntax", +] + [[package]] name = "fnv" version = "1.0.7" diff --git a/Cargo.toml b/Cargo.toml index 250e3640..ea7bb1a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/edict", "crates/edict-cli", "crates/edict-provider-host-wasmtime", "crates/edict-provider-schema", diff --git a/README.md b/README.md index 94ccc404..0812ef9a 100644 --- a/README.md +++ b/README.md @@ -517,11 +517,12 @@ input. The full stream contract and JSON Schemas are in the ### Using the library -`edict-syntax` is the front end. The one-call entry point parses and -surface-validates a source string: +The curated `edict` facade is the recommended Rust entry point. Its one-call +check parses and surface-validates a source string without exposing the +implementation crate's module tree: ```rust -use edict_syntax::{check, CheckOutcome}; +use edict::{check, CheckOutcome}; match check("package examples.hello@1;\n") { CheckOutcome::Valid => println!("ok"), @@ -530,8 +531,10 @@ match check("package examples.hello@1;\n") { } ``` -The underlying stages (`parse_module` then `validate_surface`) remain available -when you need the parsed module. +The implementation crate retains lower-level stages for repository-internal +consumers that need the parsed module. The curated facade remains +`publish = false`; the current release-engineering work does not authorize or +claim crates.io publication. --- diff --git a/crates/edict/Cargo.toml b/crates/edict/Cargo.toml new file mode 100644 index 00000000..47c65636 --- /dev/null +++ b/crates/edict/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "flyingrobots-edict" +version = "0.11.0-alpha.1" +description = "Curated public Rust facade for the Edict compiler and canonical artifacts" +readme = "README.md" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false +include = ["src/**", "tests/**", "README.md"] + +[lib] +name = "edict" + +[dependencies] +edict-syntax = { path = "../edict-syntax", version = "=0.11.0-alpha.1" } + +[lints] +workspace = true diff --git a/crates/edict/README.md b/crates/edict/README.md new file mode 100644 index 00000000..a1ecf50b --- /dev/null +++ b/crates/edict/README.md @@ -0,0 +1,24 @@ +# Edict for Rust + +`flyingrobots-edict` exposes the Rust library name `edict`. It is the curated +facade for Edict source checking, stable diagnostic classifications, and +canonical semantic-artifact identity operations. + +The complete application-build boundary remains the JSONL `edict` CLI. This +facade deliberately does not expose the implementation crate's full module +tree. + +```rust +use edict::{check, CheckOutcome}; + +assert_eq!( + check("package examples.public_facade@1;\n"), + CheckOutcome::Valid +); +``` + +## Publication status + +This package currently has `publish = false`. Its archive and dependency +closure are under release-engineering review. Nothing in this package grants +permission to publish it to crates.io. diff --git a/crates/edict/src/lib.rs b/crates/edict/src/lib.rs new file mode 100644 index 00000000..7557dafc --- /dev/null +++ b/crates/edict/src/lib.rs @@ -0,0 +1,46 @@ +//! Curated public Rust facade for Edict. +//! +//! The [`check`] entry point is the supported one-call source check. Stable +//! failure classifications are grouped under [`diagnostic`], while canonical +//! semantic-artifact identity operations are grouped under [`artifact`]. The +//! implementation crate's broad module tree is intentionally not re-exported. +//! +//! ``` +//! use edict::{check, CheckOutcome}; +//! +//! assert_eq!( +//! check("package examples.public_facade@1;\n"), +//! CheckOutcome::Valid +//! ); +//! ``` +//! +//! Implementation modules are not part of this facade: +//! +//! ```compile_fail +//! use edict::parser::parse_module; +//! ``` + +pub use edict_syntax::{check, CheckOutcome}; + +/// Stable machine-usable failure classifications exposed by the public +/// facade. +pub mod diagnostic { + pub use edict_syntax::{ + CanonicalError, CanonicalErrorKind, CompilerError, CompilerErrorKind, CompilerStage, + ParseError, ParseErrorKind, ResultProjectionFailure, ResultProjectionFailureKind, + SemanticError, SemanticErrorKind, TargetLoweringFailure, TargetLoweringFailureKind, + }; +} + +/// Canonical semantic-artifact values, encoders, and domain-framed identity +/// operations. +pub mod artifact { + pub use edict_syntax::{ + decode_canonical_cbor, decode_result_projection, digest_core_module, + digest_result_projection, digest_target_ir_artifact, encode_core_module, + encode_result_projection, encode_target_ir_artifact, verify_result_projection, + CanonicalError, CanonicalErrorKind, CoreDigest, CoreModule, ResultProjection, + ResultProjectionArtifact, ResultProjectionFailure, ResultProjectionFailureKind, + TargetIrArtifact, + }; +} diff --git a/crates/edict/tests/public_surface.rs b/crates/edict/tests/public_surface.rs new file mode 100644 index 00000000..e0acd4f5 --- /dev/null +++ b/crates/edict/tests/public_surface.rs @@ -0,0 +1,48 @@ +use edict::{ + artifact::{ + digest_core_module, digest_result_projection, digest_target_ir_artifact, CoreDigest, + ResultProjection, ResultProjectionArtifact, TargetIrArtifact, + }, + check, + diagnostic::{ + CompilerErrorKind, ParseErrorKind, ResultProjectionFailureKind, SemanticErrorKind, + TargetLoweringFailureKind, + }, + CheckOutcome, +}; + +#[test] +fn curated_facade_exposes_check_diagnostics_and_artifact_identity() { + assert_eq!( + check("package examples.public_surface@1;\n"), + CheckOutcome::Valid + ); + + assert_eq!(std::mem::size_of_val(&digest_core_module), 0); + assert_eq!(std::mem::size_of_val(&digest_target_ir_artifact), 0); + assert_eq!(std::mem::size_of_val(&digest_result_projection), 0); + + let artifact_type_sizes = [ + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + ]; + assert!(artifact_type_sizes.into_iter().all(|size| size > 0)); + + let stable_failure_kind_sizes = [ + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + ]; + assert!(stable_failure_kind_sizes.into_iter().all(|size| size > 0)); +} + +#[test] +fn implementation_modules_are_compile_fail_doctested() { + let facade = include_str!("../src/lib.rs"); + assert!(facade.contains("```compile_fail\n//! use edict::parser::parse_module;")); +} diff --git a/docs/topics/README.md b/docs/topics/README.md index dd1bafe5..99b06d31 100644 --- a/docs/topics/README.md +++ b/docs/topics/README.md @@ -68,6 +68,9 @@ cargo xtask verify validation, explicit in-process compatibility adapters for the current built-in target lowerers, the external provider WIT contract, and pure invocation request/result validation with host-authored output identity. +- [Public Rust API](./public-rust-api/README.md): curated non-publishing Rust + facade for source checking, stable diagnostic kinds, and canonical artifact + identities. - [Result Projections](./result-projections/README.md): compiler-owned, canonical, bounded application-result assembly from declared input and capability-result sources with independent reverse verification. diff --git a/docs/topics/public-rust-api/README.md b/docs/topics/public-rust-api/README.md new file mode 100644 index 00000000..98dc3024 --- /dev/null +++ b/docs/topics/public-rust-api/README.md @@ -0,0 +1,16 @@ +# Public Rust API + +Status: current HEAD contract. + +The `flyingrobots-edict` package exposes the Rust library name `edict` as a +curated facade over Edict's implementation crates. The facade is the supported +Rust entry point for source checking, stable diagnostic kinds, and canonical +artifact identity operations. It does not expose the implementation crate's +module tree as an accidental public API. + +The package remains `publish = false`. This topic defines a reversible release- +engineering boundary; it does not authorize or claim crates.io publication. + +The `edict` CLI remains the stable process boundary for complete application +builds. The Rust facade does not duplicate the CLI's JSONL protocol, provider +host, filesystem publication, or application-build orchestration. diff --git a/docs/topics/public-rust-api/test-plan.md b/docs/topics/public-rust-api/test-plan.md new file mode 100644 index 00000000..3f99edce --- /dev/null +++ b/docs/topics/public-rust-api/test-plan.md @@ -0,0 +1,44 @@ +# Public Rust API Test Plan + +## Scope + +In scope: + +- a curated Rust facade with the library name `edict`; +- source checking and stable diagnostic-kind access; +- canonical Core, Target IR, and result-projection identity access; +- package inventory and clean external-consumer checks; +- an explicit non-publication boundary. + +Out of scope: + +- crates.io publication or crate-name reservation; +- a stable 1.0 API; +- exposing the implementation crate's full module tree; +- replacing the CLI application-build boundary; +- splitting every compiler subsystem into its final crate. + +## Requirements + +| ID | Status | Requirement | Source | +| --- | --- | --- | --- | +| PUBRUST-REQ-001 | implemented | One curated package exposes Edict source checking, stable diagnostic kinds, and canonical artifact identity operations without re-exporting the implementation module tree. | issue #189 | +| PUBRUST-REQ-002 | planned | The facade's package inventory is explicit, reproducible, and remains non-publishing until a separately approved publication policy exists. | issue #189 | +| PUBRUST-REQ-003 | planned | A clean external consumer can compile against the facade without an undocumented repository-relative dependency. | issue #189 | + +## Test Cases + +| ID | Status | Category | Requirement | Oracle | Evidence | Fixtures | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | +| PUBRUST-TP-001 | implemented | Public API | PUBRUST-REQ-001 | The consumer compiles and source checking returns `CheckOutcome::Valid`. | `curated_facade_exposes_check_diagnostics_and_artifact_identity` | crates/edict/tests/public_surface.rs | Imports check, diagnostic, and artifact-identity roles through `edict`. | +| PUBRUST-TP-002 | implemented | Negative compile | PUBRUST-REQ-001 | The implementation module tree is unavailable through `edict`. | implementation_modules_are_compile_fail_doctested | crates/edict/src/lib.rs, crates/edict/tests/public_surface.rs | The integration witness binds this row to the `compile_fail` doctest attempted by the workspace test pass. | +| PUBRUST-TP-003 | planned | Package boundary | PUBRUST-REQ-002 | Packaging succeeds with the reviewed inventory without publishing or mutating registry state. | release-engineering package check | crates/edict/Cargo.toml | The current package inventory dry run succeeds; the complete registry dependency closure remains unpublished. | +| PUBRUST-TP-004 | planned | External consumer | PUBRUST-REQ-003 | The project compiles and runs without a sibling Edict checkout. | release-engineering external-consumer check | - | Requires packaged implementation dependencies or a sealed local registry before publication. | + +## Known Gaps + +- The implementation dependency still needs a permanent registry package name + and a completed dependency-closure dry run before publication can be + considered. +- Registry names, ownership, credentials, and publication automation remain + deliberately unconfigured. diff --git a/fixtures/providers/components/inventory.json b/fixtures/providers/components/inventory.json index 10627bff..9be5e9d8 100644 --- a/fixtures/providers/components/inventory.json +++ b/fixtures/providers/components/inventory.json @@ -7,5 +7,5 @@ "malformed-lowerer": "sha256:dfcd171918373d18b9dff16778e98b7618eeb4ac85976dd7134b9e201562f41b", "verifier": "sha256:9fa8e16ed7735075d559e3094685ce846d06425b4bb479be31f7498417bf87e4" }, - "sourceDigest": "sha256:246d066940659beb47de65f7d3fde09a0a4a22a23cca2358ba48a2295a781684" + "sourceDigest": "sha256:a540ebcaae3324362ec6c81fbb59f737df970e4747da0015e8097965044060b7" } From 78f8c812d182d7060caff1d24f50520195ce2b21 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 24 Aug 2026 08:51:30 -0700 Subject: [PATCH 02/10] Document durable decision policy --- AGENTS.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index b4ef5ec0..8c252d77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,6 +84,40 @@ request body or final report. - Avoid ceremonial documentation. Update shelves because the contract changed, not because a path changed. +## Durable Decision Discipline + +Important decisions are incomplete until their durable owner is current. +Architecture, authority, identity, canonical-format, recovery, compatibility, +ownership, public-API, and release-boundary decisions MUST be recorded in the +same change in the canonical topic shelf, specification, requirement, or release +document that owns the concept. Chat transcripts, Think memories, pull-request +prose, and review threads may explain or motivate a decision, but they are not +its canonical repository home. + +For every such decision: + +1. Identify one canonical owner before completing the change. Prefer the + relevant `docs/topics//README.md` for current behavior, + `architecture.md` for machinery, a normative `docs/SPEC_*.md` or ABI schema + for protocol law, and `test-plan.md` for planned and implemented evidence. +2. Record the accepted rule, its current-versus-target posture, and explicit + refinement, supersession, dependency, and related-document edges. +3. Update `docs/topics/README.md`, `docs/README.md`, or another relevant router + when a durable page or topic shelf is added, moved, or renamed. +4. Link to the canonical owner from reader-specific pages instead of copying + the same rule into several places. +5. Keep implementation checklists, review state, and delivery status in GitHub. + Current topic shelves describe branch or HEAD truth; they are not a second + project tracker. +6. Revisit the same canonical owner whenever later work refines the decision. + A refinement is not complete while code, schemas, packages, fixtures, or + release behavior disagree with the documented rule. + +Treat missing or stale canonical decision documentation as incomplete +engineering work, not optional polish. Historical design and release documents +remain evidence; update the current owning shelf rather than silently relying +on an old decision record. + ## RED/GREEN Testing Discipline Edict uses RED/GREEN test-driven development for nontrivial changes. The shared From 53be3937a1811b18147fff3f7dde075db4e88188 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 10:09:31 -0700 Subject: [PATCH 03/10] fix(release): advance facade versions during preparation --- CHANGELOG.md | 3 ++ docs/topics/public-rust-api/README.md | 5 ++ docs/topics/public-rust-api/test-plan.md | 2 + docs/topics/release-process/README.md | 5 +- docs/topics/release-process/test-plan.md | 1 + xtask/src/release_prep.rs | 30 +++++++++++- xtask/src/tests.rs | 60 +++++++++++++++++++++++- 7 files changed, 102 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caeaf4f1..23cfeee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ versions still track specification maturity rather than a released product. ### Changed +- Kept the Rust facade package version, exact implementation dependency, and + lockfile entry synchronized during release preparation, so the prepared + workspace resolves without repairing its lockfile. - Updated the capability-denied provider host from Wasmtime 46.0.2 to 46.0.3 after RUSTSEC-2026-0268 and RUSTSEC-2026-0269 made the prior exact pin fail the mandatory supply-chain gate. The direct feature and single-owner diff --git a/docs/topics/public-rust-api/README.md b/docs/topics/public-rust-api/README.md index 98dc3024..2a47401a 100644 --- a/docs/topics/public-rust-api/README.md +++ b/docs/topics/public-rust-api/README.md @@ -14,3 +14,8 @@ engineering boundary; it does not authorize or claim crates.io publication. The `edict` CLI remains the stable process boundary for complete application builds. The Rust facade does not duplicate the CLI's JSONL protocol, provider host, filesystem publication, or application-build orchestration. + +Release preparation advances the facade package version, its exact +`edict-syntax` requirement, and both lockfile package entries together. The +prepared workspace remains resolvable with offline, locked Cargo metadata. +[PUBRUST-REQ-004] diff --git a/docs/topics/public-rust-api/test-plan.md b/docs/topics/public-rust-api/test-plan.md index 3f99edce..14a8b7f4 100644 --- a/docs/topics/public-rust-api/test-plan.md +++ b/docs/topics/public-rust-api/test-plan.md @@ -25,6 +25,7 @@ Out of scope: | PUBRUST-REQ-001 | implemented | One curated package exposes Edict source checking, stable diagnostic kinds, and canonical artifact identity operations without re-exporting the implementation module tree. | issue #189 | | PUBRUST-REQ-002 | planned | The facade's package inventory is explicit, reproducible, and remains non-publishing until a separately approved publication policy exists. | issue #189 | | PUBRUST-REQ-003 | planned | A clean external consumer can compile against the facade without an undocumented repository-relative dependency. | issue #189 | +| PUBRUST-REQ-004 | implemented | Release preparation advances the facade package version and exact implementation dependency together. | xtask/src/release_prep.rs | ## Test Cases @@ -34,6 +35,7 @@ Out of scope: | PUBRUST-TP-002 | implemented | Negative compile | PUBRUST-REQ-001 | The implementation module tree is unavailable through `edict`. | implementation_modules_are_compile_fail_doctested | crates/edict/src/lib.rs, crates/edict/tests/public_surface.rs | The integration witness binds this row to the `compile_fail` doctest attempted by the workspace test pass. | | PUBRUST-TP-003 | planned | Package boundary | PUBRUST-REQ-002 | Packaging succeeds with the reviewed inventory without publishing or mutating registry state. | release-engineering package check | crates/edict/Cargo.toml | The current package inventory dry run succeeds; the complete registry dependency closure remains unpublished. | | PUBRUST-TP-004 | planned | External consumer | PUBRUST-REQ-003 | The project compiles and runs without a sibling Edict checkout. | release-engineering external-consumer check | - | Requires packaged implementation dependencies or a sealed local registry before publication. | +| PUBRUST-TP-005 | implemented | Release preparation | PUBRUST-REQ-004 | Cargo resolves the requested facade and implementation versions with the prepared lockfile. | release_prep_keeps_facade_exact_dependency_resolvable | xtask/src/tests.rs | Offline temporary workspace; no registry publication. | ## Known Gaps diff --git a/docs/topics/release-process/README.md b/docs/topics/release-process/README.md index f5bb3579..98cfd166 100644 --- a/docs/topics/release-process/README.md +++ b/docs/topics/release-process/README.md @@ -58,7 +58,10 @@ versions, a dated changelog section, release policy boundary block, release notes stub, release boundary test stub, changelog date guard entry, and paired release-process test-plan rows. The command does not write the release thesis, replace topic-shelf audits, or decide scope/non-goals; reviewers must replace -the scaffold placeholders before release. [RELEASE-REQ-024] +the scaffold placeholders before release. The facade package version, exact +implementation dependency, and lockfile entry advance with the implementation +and CLI package versions, so the prepared workspace remains resolvable. +[RELEASE-REQ-024] Every release-prep branch must audit `docs/topics/` coverage and accuracy before the release-prep pull request opens. Coverage is audited topic shelves divided diff --git a/docs/topics/release-process/test-plan.md b/docs/topics/release-process/test-plan.md index 79e64c9c..59278c08 100644 --- a/docs/topics/release-process/test-plan.md +++ b/docs/topics/release-process/test-plan.md @@ -101,6 +101,7 @@ Out of scope: | RELEASE-TP-017 | implemented | Boundary guard | RELEASE-REQ-022 | Structured policy captures the v0.10 first public CLI scope, JSONL check workflow, deterministic input expansion, stream record schemas, stable diagnostic kind codes, golden fixture corpus, and explicit non-goals for compile/lower/explain/bundle/admission commands, human-pretty output, embedded schema validation, language server, marketplace packaging, participant policy, and crates.io publication. | release_policy_tracks_v0_10_boundary | docs/topics/release-process/policy.toml | Prevents the release metadata from overclaiming the first public CLI milestone. | | RELEASE-TP-018 | implemented | Boundary guard | RELEASE-REQ-023 | Structured policy captures the v0.11 contract-bundle assembly and canonical Target IR artifact freeze scope, including semantic/release bundle digest goldens, Target IR byte/digest goldens, computed bundle integration, and explicit non-goals for runtime execution, admission execution, participant policy logic, Echo verifier completeness, git-warp commit creation, git-warp CRDT reducer verification, general target plugin dispatch, additional target profiles, extra source-to-target fixtures, canonical bundle-manifest bytes, and crates.io publication. | release_policy_tracks_v0_11_boundary | docs/topics/release-process/policy.toml | Prevents the release metadata from overclaiming the v0.11 cryptographic freeze. | | RELEASE-TP-019 | implemented | Scaffolding guard | RELEASE-REQ-024 | Given a temp repo skeleton with the current release-process surfaces, `cargo xtask release-prep ` writes the version bump, lockfile package versions, dated changelog section, release policy boundary block, release notes stub, boundary test stub, changelog date guard entry, and paired planned release-process rows deterministically. | release_prep_scaffolds_version_policy_changelog_and_test_stub | xtask/src/release_prep.rs, xtask/src/tests.rs | Keeps release-prep setup mechanical so review focuses on release thesis, scope, non-goals, and evidence rather than missed boilerplate. | +| RELEASE-TP-030 | implemented | Package versions | RELEASE-REQ-024 | After release preparation, Cargo resolves the facade exact implementation dependency with the requested package versions and an unchanged lockfile. | release_prep_keeps_facade_exact_dependency_resolvable | xtask/src/tests.rs | Offline Cargo metadata over a temporary workspace checks package versions, dependency requirements, and lockfile consistency. | ## Determinism Obligations diff --git a/xtask/src/release_prep.rs b/xtask/src/release_prep.rs index ebcfcea8..b616ac0e 100644 --- a/xtask/src/release_prep.rs +++ b/xtask/src/release_prep.rs @@ -2,6 +2,8 @@ use std::collections::BTreeSet; use std::fs; use std::path::{Path, PathBuf}; +use regex::Regex; + use crate::util::read_to_string; #[derive(Debug, Clone, PartialEq, Eq)] @@ -63,6 +65,7 @@ fn parse_decimal(part: &str, name: &str) -> Result { pub(crate) fn release_prep(root: &Path, input: &str) -> Result<(), String> { let version = ReleasePrepVersion::parse(input)?; let cli_manifest_path = root.join("crates/edict-cli/Cargo.toml"); + let facade_manifest_path = root.join("crates/edict/Cargo.toml"); let syntax_manifest_path = root.join("crates/edict-syntax/Cargo.toml"); let lockfile_path = root.join("Cargo.lock"); let changelog_path = root.join("CHANGELOG.md"); @@ -88,6 +91,10 @@ pub(crate) fn release_prep(root: &Path, input: &str) -> Result<(), String> { &read_to_string(&syntax_manifest_path)?, &version.package_version, )?; + let facade_manifest = replace_facade_manifest_version( + &read_to_string(&facade_manifest_path)?, + &version.package_version, + )?; let lockfile = replace_lock_package_versions(&read_to_string(&lockfile_path)?, &version.package_version)?; let changelog = insert_release_changelog_section( @@ -106,6 +113,7 @@ pub(crate) fn release_prep(root: &Path, input: &str) -> Result<(), String> { write_file(&cli_manifest_path, &cli_manifest)?; write_file(&syntax_manifest_path, &syntax_manifest)?; + write_file(&facade_manifest_path, &facade_manifest)?; write_file(&lockfile_path, &lockfile)?; write_file(&changelog_path, &changelog)?; write_file(&policy_path, &policy)?; @@ -144,6 +152,24 @@ fn replace_first_version_line(text: &str, package_version: &str) -> Result Result { + let text = replace_first_version_line(text, package_version)?; + // The facade declares its exact implementation requirement in an inline + // dependency table. Reject an absent or ambiguous requirement before writes. + let pattern = Regex::new( + r#"(?m)^([ \t]*edict-syntax[ \t]*=[ \t]*\{[^\r\n}]*\bversion[ \t]*=[ \t]*)"[^"]*""#, + ) + .map_err(|error| format!("facade dependency pattern: {error}"))?; + if pattern.captures_iter(&text).count() != 1 { + return Err("facade manifest must declare one inline edict-syntax version".into()); + } + Ok(pattern + .replace(&text, |captures: ®ex::Captures<'_>| { + format!("{}\"={package_version}\"", &captures[1]) + }) + .into_owned()) +} + fn replace_lock_package_versions(text: &str, package_version: &str) -> Result { let mut current_package = None; let mut replaced = BTreeSet::new(); @@ -162,7 +188,7 @@ fn replace_lock_package_versions(text: &str, package_version: &str) -> Result Result Date: Mon, 7 Sep 2026 10:16:07 -0700 Subject: [PATCH 04/10] fix(api): export complete facade artifact value models --- CHANGELOG.md | 3 + crates/edict/src/lib.rs | 17 ++- crates/edict/tests/artifact_models.rs | 146 +++++++++++++++++++++++ docs/topics/public-rust-api/README.md | 6 + docs/topics/public-rust-api/test-plan.md | 1 + 5 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 crates/edict/tests/artifact_models.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 23cfeee4..d1f2e0c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ versions still track specification maturity rather than a released product. ### Changed +- Completed the Rust facade value-model exports for Core, Target IR, result + projections, decoded canonical values, and diagnostic spans, allowing + consumers to construct artifact inputs and inspect operation results. - Kept the Rust facade package version, exact implementation dependency, and lockfile entry synchronized during release preparation, so the prepared workspace resolves without repairing its lockfile. diff --git a/crates/edict/src/lib.rs b/crates/edict/src/lib.rs index 7557dafc..169d8207 100644 --- a/crates/edict/src/lib.rs +++ b/crates/edict/src/lib.rs @@ -28,19 +28,26 @@ pub mod diagnostic { pub use edict_syntax::{ CanonicalError, CanonicalErrorKind, CompilerError, CompilerErrorKind, CompilerStage, ParseError, ParseErrorKind, ResultProjectionFailure, ResultProjectionFailureKind, - SemanticError, SemanticErrorKind, TargetLoweringFailure, TargetLoweringFailureKind, + SemanticError, SemanticErrorKind, Span, TargetLoweringFailure, TargetLoweringFailureKind, }; } /// Canonical semantic-artifact values, encoders, and domain-framed identity -/// operations. +/// operations. Nested value models are exported so callers can construct +/// candidates and inspect results without importing implementation modules. pub mod artifact { pub use edict_syntax::{ decode_canonical_cbor, decode_result_projection, digest_core_module, digest_result_projection, digest_target_ir_artifact, encode_core_module, encode_result_projection, encode_target_ir_artifact, verify_result_projection, - CanonicalError, CanonicalErrorKind, CoreDigest, CoreModule, ResultProjection, - ResultProjectionArtifact, ResultProjectionFailure, ResultProjectionFailureKind, - TargetIrArtifact, + CanonicalError, CanonicalErrorKind, CanonicalValue, CompareOp, CoreBlock, CoreBound, + CoreBudget, CoreDigest, CoreExpr, CoreExternalActionBudget, CoreImport, CoreImportKind, + CoreIntent, CoreModule, CoreNode, CoreObstructionArm, CoreObstructionReason, CorePredicate, + CoreRequireFailureArm, CoreType, CoreValue, InputConstraint, InputConstraintSource, + LocalRef, ResourceRef, ResultProjection, ResultProjectionArtifact, ResultProjectionExpr, + ResultProjectionFailure, ResultProjectionFailureKind, ResultProjectionSource, + TargetIrArtifact, TargetIrExternalActionRequest, TargetIrIntent, TargetIrPureBinding, + TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, TargetIrStep, + VerifiedResultProjection, }; } diff --git a/crates/edict/tests/artifact_models.rs b/crates/edict/tests/artifact_models.rs new file mode 100644 index 00000000..b1d4ce26 --- /dev/null +++ b/crates/edict/tests/artifact_models.rs @@ -0,0 +1,146 @@ +//! Separate consumer crate: every Edict name comes from the curated facade. +use std::collections::BTreeMap; + +use edict::{ + artifact::{ + decode_canonical_cbor, decode_result_projection, digest_core_module, + digest_result_projection, digest_target_ir_artifact, encode_core_module, + encode_result_projection, encode_target_ir_artifact, verify_result_projection, + CanonicalValue, CoreBlock, CoreBudget, CoreExpr, CoreIntent, CoreModule, CoreType, + LocalRef, ResourceRef, ResultProjection, ResultProjectionExpr, ResultProjectionSource, + TargetIrArtifact, TargetIrIntent, TargetIrSemanticClosure, VerifiedResultProjection, + }, + check, + diagnostic::Span, + CheckOutcome, +}; + +fn core_fixture() -> CoreModule { + let input = LocalRef { + id: "arg.0".to_owned(), + alpha_name: "input".to_owned(), + ty: "examples.facade@1.Input".to_owned(), + }; + let result = CoreExpr::Local { + reference: input.clone(), + }; + CoreModule { + api_version: "edict.core/v1".to_owned(), + coordinate: "examples.facade@1".to_owned(), + imports: Vec::new(), + types: BTreeMap::from([( + "Input".to_owned(), + CoreType::Record { + fields: BTreeMap::from([("ok".to_owned(), "Bool".to_owned())]), + }, + )]), + intents: BTreeMap::from([( + "echo".to_owned(), + CoreIntent { + input: input.ty.clone(), + output: input.ty.clone(), + required_operation_profile: "continuum.profile.read-only/v1".to_owned(), + basis: Some(result.clone()), + input_constraints: Vec::new(), + core_evaluation_budget: CoreBudget { + max_steps: 8, + max_allocated_bytes: 256, + max_output_bytes: 64, + }, + body: CoreBlock { + locals: vec![input], + nodes: Vec::new(), + result, + }, + }, + )]), + required_core_capabilities: Vec::new(), + } +} + +fn target_fixture(core: &CoreModule) -> TargetIrArtifact { + let intent = &core.intents["echo"]; + TargetIrArtifact { + domain: "echo.span-ir/v1".to_owned(), + target_profile: ResourceRef { + coordinate: "echo.dpo@1".to_owned(), + digest: Some(format!("sha256:{}", "1".repeat(64))), + }, + source_core_coordinate: core.coordinate.clone(), + semantic_closure: Some(TargetIrSemanticClosure { + source_core: ResourceRef { + coordinate: core.coordinate.clone(), + digest: Some( + digest_core_module(core) + .expect("Core digest") + .to_review_string(), + ), + }, + lawpacks: Vec::new(), + capabilities: Vec::new(), + }), + intents: BTreeMap::from([( + "echo".to_owned(), + TargetIrIntent { + operation_profile: intent.required_operation_profile.clone(), + basis: intent.basis.clone(), + input_constraints: intent.input_constraints.clone(), + core_evaluation_budget: intent.core_evaluation_budget.clone(), + pure_bindings: Vec::new(), + requirements: Vec::new(), + steps: Vec::new(), + external_action_requests: Vec::new(), + result: intent.body.result.clone(), + }, + )]), + } +} + +#[test] +fn facade_consumer_constructs_and_verifies_artifacts() { + let core = core_fixture(); + let target = target_fixture(&core); + let projection = ResultProjection { + api_version: "edict.result-projection/v1".to_owned(), + operation_coordinate: "examples.facade@1.echo".to_owned(), + output_type: "examples.facade@1.Input".to_owned(), + max_output_bytes: 64, + expression: ResultProjectionExpr::Source { + source: ResultProjectionSource::ApplicationInput, + path: Vec::new(), + }, + }; + + let core_bytes = encode_core_module(&core).expect("encode consumer Core"); + let target_bytes = encode_target_ir_artifact(&target).expect("encode consumer Target IR"); + let projection_bytes = encode_result_projection(&projection).expect("encode projection"); + let decoded: CanonicalValue = decode_canonical_cbor(&core_bytes).expect("decode Core value"); + assert!(matches!(decoded, CanonicalValue::Map(_))); + assert!(matches!( + decode_canonical_cbor(&target_bytes).expect("decode Target IR value"), + CanonicalValue::Map(_) + )); + assert_eq!( + decode_result_projection(&projection_bytes).expect("decode projection"), + projection + ); + let projection_digest = digest_result_projection(&projection).expect("projection digest"); + let verified: VerifiedResultProjection = + verify_result_projection(&core, &target, "echo", &projection_bytes, projection_digest) + .expect("independently verify consumer projection"); + assert_eq!(verified.projection(), &projection); + assert_eq!(verified.digest(), projection_digest); + assert_ne!( + digest_core_module(&core).expect("Core identity"), + digest_target_ir_artifact(&target).expect("Target IR identity") + ); +} + +#[test] +fn facade_consumer_names_diagnostic_spans() { + let CheckOutcome::ParseFailed(error) = check("package ;") else { + panic!("malformed package must fail parsing"); + }; + let span: Span = error.span; + assert_eq!(span, Span { start: 8, end: 9 }); +} diff --git a/docs/topics/public-rust-api/README.md b/docs/topics/public-rust-api/README.md index 2a47401a..ebda0cb8 100644 --- a/docs/topics/public-rust-api/README.md +++ b/docs/topics/public-rust-api/README.md @@ -19,3 +19,9 @@ Release preparation advances the facade package version, its exact `edict-syntax` requirement, and both lockfile package entries together. The prepared workspace remains resolvable with offline, locked Cargo metadata. [PUBRUST-REQ-004] + +The artifact namespace exports the value models needed to construct Core, +Target IR, and result-projection inputs and to inspect decoded canonical values +and verified projections. Diagnostic spans are available under `diagnostic`. +These are explicit type exports; implementation modules remain private to the +facade boundary. [PUBRUST-REQ-001] diff --git a/docs/topics/public-rust-api/test-plan.md b/docs/topics/public-rust-api/test-plan.md index 14a8b7f4..7fd5088d 100644 --- a/docs/topics/public-rust-api/test-plan.md +++ b/docs/topics/public-rust-api/test-plan.md @@ -36,6 +36,7 @@ Out of scope: | PUBRUST-TP-003 | planned | Package boundary | PUBRUST-REQ-002 | Packaging succeeds with the reviewed inventory without publishing or mutating registry state. | release-engineering package check | crates/edict/Cargo.toml | The current package inventory dry run succeeds; the complete registry dependency closure remains unpublished. | | PUBRUST-TP-004 | planned | External consumer | PUBRUST-REQ-003 | The project compiles and runs without a sibling Edict checkout. | release-engineering external-consumer check | - | Requires packaged implementation dependencies or a sealed local registry before publication. | | PUBRUST-TP-005 | implemented | Release preparation | PUBRUST-REQ-004 | Cargo resolves the requested facade and implementation versions with the prepared lockfile. | release_prep_keeps_facade_exact_dependency_resolvable | xtask/src/tests.rs | Offline temporary workspace; no registry publication. | +| PUBRUST-TP-006 | implemented | Consumer model closure | PUBRUST-REQ-001 | A consumer using only facade imports constructs Core, Target IR, and projection values, names decoded values and verified projections, and reads diagnostic spans. | facade_consumer_constructs_and_verifies_artifacts, facade_consumer_names_diagnostic_spans | crates/edict/tests/artifact_models.rs | The integration test is a separate consumer crate; it uses no implementation imports. | ## Known Gaps From f9061bfff4b814da13cc0ff4decea01ef69dac52 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 10:21:19 -0700 Subject: [PATCH 05/10] test(api): exercise facade operations and consumer boundary --- Cargo.lock | 1 + crates/edict/Cargo.toml | 3 + crates/edict/tests/public_surface.rs | 110 +++++++++++++------ docs/topics/public-rust-api/test-plan.md | 4 +- fixtures/providers/components/inventory.json | 2 +- 5 files changed, 84 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d12cd1b..47dd29ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,7 @@ name = "flyingrobots-edict" version = "0.11.0-alpha.1" dependencies = [ "edict-syntax", + "serde_json", ] [[package]] diff --git a/crates/edict/Cargo.toml b/crates/edict/Cargo.toml index 47c65636..525403f4 100644 --- a/crates/edict/Cargo.toml +++ b/crates/edict/Cargo.toml @@ -16,5 +16,8 @@ name = "edict" [dependencies] edict-syntax = { path = "../edict-syntax", version = "=0.11.0-alpha.1" } +[dev-dependencies] +serde_json = "1" + [lints] workspace = true diff --git a/crates/edict/tests/public_surface.rs b/crates/edict/tests/public_surface.rs index e0acd4f5..82f21ce5 100644 --- a/crates/edict/tests/public_surface.rs +++ b/crates/edict/tests/public_surface.rs @@ -1,48 +1,92 @@ +use std::fs; +use std::path::Path; +use std::process::{Command, Output}; + use edict::{ - artifact::{ - digest_core_module, digest_result_projection, digest_target_ir_artifact, CoreDigest, - ResultProjection, ResultProjectionArtifact, TargetIrArtifact, - }, check, - diagnostic::{ - CompilerErrorKind, ParseErrorKind, ResultProjectionFailureKind, SemanticErrorKind, - TargetLoweringFailureKind, - }, + diagnostic::{ParseErrorKind, SemanticErrorKind}, CheckOutcome, }; #[test] -fn curated_facade_exposes_check_diagnostics_and_artifact_identity() { +fn curated_facade_checks_source_and_reports_stable_failures() { assert_eq!( check("package examples.public_surface@1;\n"), CheckOutcome::Valid ); + let CheckOutcome::ParseFailed(error) = check("package ;") else { + panic!("malformed package must fail parsing"); + }; + assert_eq!(error.kind, ParseErrorKind::ExpectedIdentifier); + + let CheckOutcome::SemanticFailed(errors) = + check("package examples.public_surface@1; type Input = { name: String };") + else { + panic!("unbounded string must fail semantic checking"); + }; + let kinds: Vec<_> = errors.iter().map(|error| error.kind).collect(); + assert_eq!(kinds, vec![SemanticErrorKind::UnboundedScalar]); +} + +fn check_consumer(root: &Path, source: &str) -> Output { + fs::write(root.join("src/main.rs"), source).expect("consumer source"); + Command::new("cargo") + .args(["check", "--offline", "--message-format=json"]) + // An independent target directory avoids contending with the parent + // cargo-test invocation; subsequent controls reuse the dependency build. + .arg("--target-dir") + .arg(root.join("target")) + .current_dir(root) + .output() + .expect("check independent facade consumer") +} - assert_eq!(std::mem::size_of_val(&digest_core_module), 0); - assert_eq!(std::mem::size_of_val(&digest_target_ir_artifact), 0); - assert_eq!(std::mem::size_of_val(&digest_result_projection), 0); - - let artifact_type_sizes = [ - std::mem::size_of::(), - std::mem::size_of::(), - std::mem::size_of::(), - std::mem::size_of::(), - std::mem::size_of::(), - ]; - assert!(artifact_type_sizes.into_iter().all(|size| size > 0)); - - let stable_failure_kind_sizes = [ - std::mem::size_of::(), - std::mem::size_of::(), - std::mem::size_of::(), - std::mem::size_of::(), - std::mem::size_of::(), - ]; - assert!(stable_failure_kind_sizes.into_iter().all(|size| size > 0)); +fn compiler_error_codes(output: &Output) -> Vec { + String::from_utf8(output.stdout.clone()) + .expect("Cargo diagnostic stream") + .lines() + .map(|line| serde_json::from_str::(line).expect("Cargo JSON record")) + .filter(|record| record["reason"] == "compiler-message") + .filter(|record| record["message"]["level"] == "error") + .filter_map(|record| { + record["message"]["code"]["code"] + .as_str() + .map(str::to_owned) + }) + .collect() } #[test] -fn implementation_modules_are_compile_fail_doctested() { - let facade = include_str!("../src/lib.rs"); - assert!(facade.contains("```compile_fail\n//! use edict::parser::parse_module;")); +fn implementation_modules_are_unavailable_to_consumers() { + let root = std::env::temp_dir().join(format!("edict-facade-consumer-{}", std::process::id())); + fs::create_dir(&root).expect("fresh consumer workspace"); + fs::create_dir(root.join("src")).expect("consumer source directory"); + let facade_path = + serde_json::to_string(env!("CARGO_MANIFEST_DIR")).expect("quote the facade path for TOML"); + fs::write( + root.join("Cargo.toml"), + format!( + "[package]\nname = \"facade-consumer\"\nversion = \"0.0.0\"\nedition = \"2024\"\n[workspace]\n[dependencies]\nedict = {{ package = \"flyingrobots-edict\", path = {facade_path} }}\n" + ), + ) + .expect("consumer manifest"); + + let positive = check_consumer( + &root, + "use edict::{check, CheckOutcome}; fn main() { assert_eq!(check(\"package examples.consumer@1;\"), CheckOutcome::Valid); }", + ); + assert!( + positive.status.success(), + "supported consumer must compile: {}", + String::from_utf8_lossy(&positive.stderr) + ); + assert!(compiler_error_codes(&positive).is_empty()); + + let negative = check_consumer( + &root, + "use edict::parser::parse_module; fn main() { let _ = parse_module(\"package examples.consumer@1;\"); }", + ); + assert!(!negative.status.success()); + assert_eq!(compiler_error_codes(&negative), vec!["E0432"]); + fs::remove_dir_all(&root).expect("remove owned consumer workspace"); } diff --git a/docs/topics/public-rust-api/test-plan.md b/docs/topics/public-rust-api/test-plan.md index 7fd5088d..4c3b9e96 100644 --- a/docs/topics/public-rust-api/test-plan.md +++ b/docs/topics/public-rust-api/test-plan.md @@ -31,8 +31,8 @@ Out of scope: | ID | Status | Category | Requirement | Oracle | Evidence | Fixtures | Notes | | --- | --- | --- | --- | --- | --- | --- | --- | -| PUBRUST-TP-001 | implemented | Public API | PUBRUST-REQ-001 | The consumer compiles and source checking returns `CheckOutcome::Valid`. | `curated_facade_exposes_check_diagnostics_and_artifact_identity` | crates/edict/tests/public_surface.rs | Imports check, diagnostic, and artifact-identity roles through `edict`. | -| PUBRUST-TP-002 | implemented | Negative compile | PUBRUST-REQ-001 | The implementation module tree is unavailable through `edict`. | implementation_modules_are_compile_fail_doctested | crates/edict/src/lib.rs, crates/edict/tests/public_surface.rs | The integration witness binds this row to the `compile_fail` doctest attempted by the workspace test pass. | +| PUBRUST-TP-001 | implemented | Public API | PUBRUST-REQ-001 | Source checking accepts valid input and reports stable parse and semantic failure kinds; consumer artifact values encode, decode, digest, and verify. | curated_facade_checks_source_and_reports_stable_failures, facade_consumer_constructs_and_verifies_artifacts | crates/edict/tests/public_surface.rs, crates/edict/tests/artifact_models.rs | Operations execute on valid and invalid inputs; no representation checks. | +| PUBRUST-TP-002 | implemented | Negative compile | PUBRUST-REQ-001 | A facade-only consumer compiles supported imports, then receives Rust E0432 for the implementation parser module. | implementation_modules_are_unavailable_to_consumers | crates/edict/src/lib.rs, crates/edict/tests/public_surface.rs | The independent consumer checks structured compiler diagnostics; the compile-fail doctest remains an additional workspace witness. | | PUBRUST-TP-003 | planned | Package boundary | PUBRUST-REQ-002 | Packaging succeeds with the reviewed inventory without publishing or mutating registry state. | release-engineering package check | crates/edict/Cargo.toml | The current package inventory dry run succeeds; the complete registry dependency closure remains unpublished. | | PUBRUST-TP-004 | planned | External consumer | PUBRUST-REQ-003 | The project compiles and runs without a sibling Edict checkout. | release-engineering external-consumer check | - | Requires packaged implementation dependencies or a sealed local registry before publication. | | PUBRUST-TP-005 | implemented | Release preparation | PUBRUST-REQ-004 | Cargo resolves the requested facade and implementation versions with the prepared lockfile. | release_prep_keeps_facade_exact_dependency_resolvable | xtask/src/tests.rs | Offline temporary workspace; no registry publication. | diff --git a/fixtures/providers/components/inventory.json b/fixtures/providers/components/inventory.json index e25b4c46..11bd5617 100644 --- a/fixtures/providers/components/inventory.json +++ b/fixtures/providers/components/inventory.json @@ -7,5 +7,5 @@ "malformed-lowerer": "sha256:dfcd171918373d18b9dff16778e98b7618eeb4ac85976dd7134b9e201562f41b", "verifier": "sha256:9fa8e16ed7735075d559e3094685ce846d06425b4bb479be31f7498417bf87e4" }, - "sourceDigest": "sha256:b359e2363778e59151312ae9da72f5a81933839499eae3b071abb9fa31412ab4" + "sourceDigest": "sha256:17ec99a3c3d1496177c89440b75616ee29d658560d8daf179a5e040dd4418427" } From 2134130d020c4dc586fa01bce95391dd68b73cab Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 10:22:04 -0700 Subject: [PATCH 06/10] docs: give durable decisions a canonical policy owner --- AGENTS.md | 34 +++----------------------- docs/topics/documentation/README.md | 34 ++++++++++++++++++++++++++ docs/topics/documentation/test-plan.md | 2 ++ 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8c252d77..0c48a579 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,37 +86,9 @@ request body or final report. ## Durable Decision Discipline -Important decisions are incomplete until their durable owner is current. -Architecture, authority, identity, canonical-format, recovery, compatibility, -ownership, public-API, and release-boundary decisions MUST be recorded in the -same change in the canonical topic shelf, specification, requirement, or release -document that owns the concept. Chat transcripts, Think memories, pull-request -prose, and review threads may explain or motivate a decision, but they are not -its canonical repository home. - -For every such decision: - -1. Identify one canonical owner before completing the change. Prefer the - relevant `docs/topics//README.md` for current behavior, - `architecture.md` for machinery, a normative `docs/SPEC_*.md` or ABI schema - for protocol law, and `test-plan.md` for planned and implemented evidence. -2. Record the accepted rule, its current-versus-target posture, and explicit - refinement, supersession, dependency, and related-document edges. -3. Update `docs/topics/README.md`, `docs/README.md`, or another relevant router - when a durable page or topic shelf is added, moved, or renamed. -4. Link to the canonical owner from reader-specific pages instead of copying - the same rule into several places. -5. Keep implementation checklists, review state, and delivery status in GitHub. - Current topic shelves describe branch or HEAD truth; they are not a second - project tracker. -6. Revisit the same canonical owner whenever later work refines the decision. - A refinement is not complete while code, schemas, packages, fixtures, or - release behavior disagree with the documented rule. - -Treat missing or stale canonical decision documentation as incomplete -engineering work, not optional polish. Historical design and release documents -remain evidence; update the current owning shelf rather than silently relying -on an old decision record. +Follow the canonical [durable decision policy](docs/topics/documentation/README.md#durable-decision-discipline). +Important decisions are incomplete until the owning repository document is +current in the same change; chat, memory, and PR prose do not replace that owner. ## RED/GREEN Testing Discipline diff --git a/docs/topics/documentation/README.md b/docs/topics/documentation/README.md index 1ef64094..24845b43 100644 --- a/docs/topics/documentation/README.md +++ b/docs/topics/documentation/README.md @@ -113,6 +113,40 @@ Do not copy live issue lists, pull request lists, CI timestamps, or dashboards into prose as current truth. Link to live systems or use generated artifacts when those facts matter. [DOCS-REQ-005] +## Durable Decision Discipline + +Important decisions are incomplete until their durable owner is current. +Architecture, authority, identity, canonical-format, recovery, compatibility, +ownership, public-API, and release-boundary decisions MUST be recorded in the +same change in the canonical topic shelf, specification, requirement, or release +document that owns the concept. Chat transcripts, Think memories, pull-request +prose, and review threads may explain or motivate a decision, but they are not +its canonical repository home. + +For every such decision: + +1. Identify one canonical owner before completing the change. Prefer the + relevant `docs/topics//README.md` for current behavior, + `architecture.md` for machinery, a normative `docs/SPEC_*.md` or ABI schema + for protocol law, and `test-plan.md` for planned and implemented evidence. +2. Record the accepted rule, its current-versus-target posture, and explicit + refinement, supersession, dependency, and related-document edges. +3. Update `docs/topics/README.md`, `docs/README.md`, or another relevant router + when a durable page or topic shelf is added, moved, or renamed. +4. Link to the canonical owner from reader-specific pages instead of copying + the same rule into several places. +5. Keep implementation checklists, review state, and delivery status in GitHub. + Current topic shelves describe branch or HEAD truth; they are not a second + project tracker. +6. Revisit the same canonical owner whenever later work refines the decision. + A refinement is not complete while code, schemas, packages, fixtures, or + release behavior disagree with the documented rule. + +Treat missing or stale canonical decision documentation as incomplete +engineering work, not optional polish. Historical design and release documents +remain evidence; update the current owning shelf rather than silently relying +on an old decision record. [DOCS-REQ-007] + ## Deterministic checks and editorial review The local gate already checks links, topic metadata, evidence names, fixture diff --git a/docs/topics/documentation/test-plan.md b/docs/topics/documentation/test-plan.md index a2d41841..20f085b7 100644 --- a/docs/topics/documentation/test-plan.md +++ b/docs/topics/documentation/test-plan.md @@ -34,6 +34,7 @@ Out of scope: | DOCS-REQ-004 | policy | Examples distinguish runnable, illustrative, and abridged use; copyable shell commands omit prompts. | docs/topics/documentation/README.md, fixtures/README.md | | DOCS-REQ-005 | policy | Contract-bearing changes update affected docs or declare `docs-impact: none`; source-language and Core semantic changes keep the formal language spec, coupled CDDL, and owning evidence current in the same pull request; changed documentation preserves page type. | AGENTS.md, CONTRIBUTING.md, docs/SPEC_edict-language-v1.md, docs/topics/documentation/README.md | | DOCS-REQ-006 | policy | Documentation quality uses deterministic checks for software facts and human review for reader-task success. | docs/topics/documentation/README.md, xtask/src/contract_check.rs, xtask/src/tests.rs | +| DOCS-REQ-007 | policy | Important durable decisions are complete only when their canonical repository owner and affected entry points are current in the same change. | docs/topics/documentation/README.md | ## Fixtures @@ -56,6 +57,7 @@ Out of scope: | DOCS-TP-003 | policy | Coverage policy | DOCS-REQ-003 | Review confirms the documentation shelf contains an Edict coverage matrix. | - | docs/topics/documentation/README.md | Policy detail; do not encode as a Rust test. | | DOCS-TP-004 | policy | Example and impact policy | DOCS-REQ-004, DOCS-REQ-005 | Review confirms the documentation shelf states runnable example rules, copyable shell command rules, formal language-spec/CDDL/evidence synchronization, `docs-impact: none`, and page-type preservation. | - | CONTRIBUTING.md, docs/SPEC_edict-language-v1.md, docs/topics/documentation/README.md, docs/topics/documentation/test-plan.md | Policy detail; do not encode as a Rust test. | | DOCS-TP-005 | policy | Local gate policy | DOCS-REQ-006 | Review confirms deterministic checks are described as fact checks and behavior tests, with prose quality left to human review. | - | docs/topics/documentation/README.md, docs/topics/documentation/test-plan.md | Tool behavior is covered by existing `contract_graph_*` tests. | +| DOCS-TP-006 | policy | Durable decision ownership | DOCS-REQ-007 | Review identifies one canonical owner and rejects a change whose accepted decision exists only in chat, memory, PR prose, or a stale owning page. | - | AGENTS.md, docs/topics/documentation/README.md | Human workflow contract; existing link and topic checks verify discovery, not whether prose faithfully records a decision. | ## Determinism Obligations From 2944cc625d07a07630e6b5161250a8ad7106edea Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 10:22:30 -0700 Subject: [PATCH 07/10] docs: distinguish implemented decisions from planned targets --- docs/topics/documentation/README.md | 8 ++++++-- docs/topics/documentation/test-plan.md | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/topics/documentation/README.md b/docs/topics/documentation/README.md index 24845b43..2a238a11 100644 --- a/docs/topics/documentation/README.md +++ b/docs/topics/documentation/README.md @@ -129,8 +129,12 @@ For every such decision: relevant `docs/topics//README.md` for current behavior, `architecture.md` for machinery, a normative `docs/SPEC_*.md` or ABI schema for protocol law, and `test-plan.md` for planned and implemented evidence. -2. Record the accepted rule, its current-versus-target posture, and explicit - refinement, supersession, dependency, and related-document edges. +2. Record the accepted rule and whether it is implemented or planned. Put + implemented behavior in its current-truth owner. Put target behavior in + explicitly planned `test-plan.md` rows or a linked design proposal; a topic + `README.md` may link to that future work but must not describe it as current + behavior. Record the decision's refinement, supersession, dependency, and + related-document edges in the page that owns its actual posture. 3. Update `docs/topics/README.md`, `docs/README.md`, or another relevant router when a durable page or topic shelf is added, moved, or renamed. 4. Link to the canonical owner from reader-specific pages instead of copying diff --git a/docs/topics/documentation/test-plan.md b/docs/topics/documentation/test-plan.md index 20f085b7..fedf5678 100644 --- a/docs/topics/documentation/test-plan.md +++ b/docs/topics/documentation/test-plan.md @@ -58,6 +58,7 @@ Out of scope: | DOCS-TP-004 | policy | Example and impact policy | DOCS-REQ-004, DOCS-REQ-005 | Review confirms the documentation shelf states runnable example rules, copyable shell command rules, formal language-spec/CDDL/evidence synchronization, `docs-impact: none`, and page-type preservation. | - | CONTRIBUTING.md, docs/SPEC_edict-language-v1.md, docs/topics/documentation/README.md, docs/topics/documentation/test-plan.md | Policy detail; do not encode as a Rust test. | | DOCS-TP-005 | policy | Local gate policy | DOCS-REQ-006 | Review confirms deterministic checks are described as fact checks and behavior tests, with prose quality left to human review. | - | docs/topics/documentation/README.md, docs/topics/documentation/test-plan.md | Tool behavior is covered by existing `contract_graph_*` tests. | | DOCS-TP-006 | policy | Durable decision ownership | DOCS-REQ-007 | Review identifies one canonical owner and rejects a change whose accepted decision exists only in chat, memory, PR prose, or a stale owning page. | - | AGENTS.md, docs/topics/documentation/README.md | Human workflow contract; existing link and topic checks verify discovery, not whether prose faithfully records a decision. | +| DOCS-TP-007 | policy | Decision posture | DOCS-REQ-007 | Review keeps implemented rules in current-truth pages and planned target rules in explicitly planned evidence or linked proposals; an implemented rule may link to future work without claiming it exists. | - | AGENTS.md, docs/topics/documentation/README.md | Resolve current-versus-target placement without turning topic READMEs into delivery trackers. | ## Determinism Obligations From 735b6458a7a8f9189036e14ca81a77458233b4ca Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 10:23:11 -0700 Subject: [PATCH 08/10] docs: define durable decision relationship records --- docs/topics/documentation/README.md | 31 ++++++++++++++++++++++++++ docs/topics/documentation/test-plan.md | 1 + 2 files changed, 32 insertions(+) diff --git a/docs/topics/documentation/README.md b/docs/topics/documentation/README.md index 2a238a11..34fa7f4d 100644 --- a/docs/topics/documentation/README.md +++ b/docs/topics/documentation/README.md @@ -151,6 +151,37 @@ engineering work, not optional polish. Historical design and release documents remain evidence; update the current owning shelf rather than silently relying on an old decision record. [DOCS-REQ-007] +### Decision relationship format + +Place a two-column `Relationship` / `Targets` table beside the decision's rule +and posture. Include all four field names below, in this order. Each target is +a Markdown link to the canonical document or section; use the requirement or +decision ID as the link label when one exists. Separate multiple targets with +commas. Write the literal `none` when a relationship has no targets; an omitted +row or blank cell is incomplete. [DOCS-REQ-007] + +| Field | Meaning from this decision to its target | +| --- | --- | +| `refines` | Adds detail or a narrower rule while the target remains authoritative. | +| `supersedes` | Replaces the target rule. Preserve its history and identify this replacement in the prior owner. | +| `depends_on` | Requires the target contract to hold for this decision to hold. | +| `related` | Provides relevant context without asserting refinement, replacement, or dependency. | + +For example, this durable-decision policy has current posture and the following +relationships. It refines the documentation-impact rule by requiring a named +canonical owner; it depends on the existing topic-shelf contract. + +| Relationship | Targets | +| --- | --- | +| `refines` | [DOCS-REQ-005](./test-plan.md#requirements) | +| `supersedes` | none | +| `depends_on` | [Topic shelf contract](../README.md) | +| `related` | [Review process](../review-process/README.md) | + +These edges are reviewed as policy metadata. Link and topic checks verify the +local references and evidence records; they do not prove that a claimed +refinement, supersession, or dependency is semantically correct. + ## Deterministic checks and editorial review The local gate already checks links, topic metadata, evidence names, fixture diff --git a/docs/topics/documentation/test-plan.md b/docs/topics/documentation/test-plan.md index fedf5678..a9e66083 100644 --- a/docs/topics/documentation/test-plan.md +++ b/docs/topics/documentation/test-plan.md @@ -59,6 +59,7 @@ Out of scope: | DOCS-TP-005 | policy | Local gate policy | DOCS-REQ-006 | Review confirms deterministic checks are described as fact checks and behavior tests, with prose quality left to human review. | - | docs/topics/documentation/README.md, docs/topics/documentation/test-plan.md | Tool behavior is covered by existing `contract_graph_*` tests. | | DOCS-TP-006 | policy | Durable decision ownership | DOCS-REQ-007 | Review identifies one canonical owner and rejects a change whose accepted decision exists only in chat, memory, PR prose, or a stale owning page. | - | AGENTS.md, docs/topics/documentation/README.md | Human workflow contract; existing link and topic checks verify discovery, not whether prose faithfully records a decision. | | DOCS-TP-007 | policy | Decision posture | DOCS-REQ-007 | Review keeps implemented rules in current-truth pages and planned target rules in explicitly planned evidence or linked proposals; an implemented rule may link to future work without claiming it exists. | - | AGENTS.md, docs/topics/documentation/README.md | Resolve current-versus-target placement without turning topic READMEs into delivery trackers. | +| DOCS-TP-008 | policy | Decision relationships | DOCS-REQ-007 | Review checks all four relationship fields, linked targets with their relationship direction, and explicit `none` for absent edges; superseded rules identify their replacement. | - | docs/topics/documentation/README.md | The canonical table format supports consistent human review; no semantic graph checker is claimed. | ## Determinism Obligations From f9b8ce00a21a1d1440973a698879b37c7beed89e Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 10:31:32 -0700 Subject: [PATCH 09/10] docs: record facade decision contract relationships --- ARCHITECTURE.md | 4 +++- docs/topics/documentation/test-plan.md | 2 +- docs/topics/public-rust-api/README.md | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 38ba435b..5b513dd2 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -32,7 +32,9 @@ runtime without exposing Wasmtime types through Edict contracts. `flyingrobots-edict` exposes the Rust library name `edict`. It is the curated public facade for source checking, stable diagnostic classifications, and canonical Core, Target IR, and result-projection artifact identity operations. -It deliberately does not re-export the implementation crate's module tree. +The [public Rust API topic](docs/topics/public-rust-api/README.md) owns this +boundary. The facade deliberately does not re-export the implementation crate's +module tree. The package remains `publish = false`. Its presence defines and tests the intended public Rust boundary; it neither authorizes nor claims crates.io diff --git a/docs/topics/documentation/test-plan.md b/docs/topics/documentation/test-plan.md index a9e66083..47964954 100644 --- a/docs/topics/documentation/test-plan.md +++ b/docs/topics/documentation/test-plan.md @@ -59,7 +59,7 @@ Out of scope: | DOCS-TP-005 | policy | Local gate policy | DOCS-REQ-006 | Review confirms deterministic checks are described as fact checks and behavior tests, with prose quality left to human review. | - | docs/topics/documentation/README.md, docs/topics/documentation/test-plan.md | Tool behavior is covered by existing `contract_graph_*` tests. | | DOCS-TP-006 | policy | Durable decision ownership | DOCS-REQ-007 | Review identifies one canonical owner and rejects a change whose accepted decision exists only in chat, memory, PR prose, or a stale owning page. | - | AGENTS.md, docs/topics/documentation/README.md | Human workflow contract; existing link and topic checks verify discovery, not whether prose faithfully records a decision. | | DOCS-TP-007 | policy | Decision posture | DOCS-REQ-007 | Review keeps implemented rules in current-truth pages and planned target rules in explicitly planned evidence or linked proposals; an implemented rule may link to future work without claiming it exists. | - | AGENTS.md, docs/topics/documentation/README.md | Resolve current-versus-target placement without turning topic READMEs into delivery trackers. | -| DOCS-TP-008 | policy | Decision relationships | DOCS-REQ-007 | Review checks all four relationship fields, linked targets with their relationship direction, and explicit `none` for absent edges; superseded rules identify their replacement. | - | docs/topics/documentation/README.md | The canonical table format supports consistent human review; no semantic graph checker is claimed. | +| DOCS-TP-008 | policy | Decision relationships | DOCS-REQ-007 | Review checks all four relationship fields, linked targets with their relationship direction, and explicit `none` for absent edges; superseded rules identify their replacement. | - | docs/topics/documentation/README.md, docs/topics/public-rust-api/README.md | The canonical table format supports consistent human review; no semantic graph checker is claimed. | ## Determinism Obligations diff --git a/docs/topics/public-rust-api/README.md b/docs/topics/public-rust-api/README.md index ebda0cb8..c64a1081 100644 --- a/docs/topics/public-rust-api/README.md +++ b/docs/topics/public-rust-api/README.md @@ -25,3 +25,21 @@ Target IR, and result-projection inputs and to inspect decoded canonical values and verified projections. Diagnostic spans are available under `diagnostic`. These are explicit type exports; implementation modules remain private to the facade boundary. [PUBRUST-REQ-001] + +## Decision relationships + +This current public-API boundary depends on the contracts implemented by its +explicit exports. It adds a curated entry point; the implementation crate +continues to serve repository consumers. + +| Relationship | Targets | +| --- | --- | +| `refines` | none | +| `supersedes` | none | +| `depends_on` | [Syntax](../syntax/README.md), [Semantic validation](../semantic-validation/README.md), [Core IR](../core-ir/README.md), [Target IR](../target-ir/README.md), [Result projections](../result-projections/README.md) | +| `related` | [Rust standards](../rust-standards/README.md), [Release process](../release-process/README.md), [CLI](../cli/README.md) | + +The relationship table follows the +[durable-decision policy](../documentation/README.md#durable-decision-discipline). +Packaging and registry-publication work remain explicitly planned in the +[test plan](./test-plan.md); they are not claims of current publication. From e364ee84da213bb851f502f231ccfe051db341a7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 7 Sep 2026 12:29:55 -0700 Subject: [PATCH 10/10] fix(release): validate policy calendar dates --- CHANGELOG.md | 2 ++ docs/topics/release-process/README.md | 4 +++- docs/topics/release-process/test-plan.md | 2 ++ xtask/src/release_prep.rs | 4 ++-- xtask/src/tests.rs | 28 +++++++++++++++++------- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad62fd82..74a121ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ versions still track specification maturity rather than a released product. ### Changed +- Reused release preparation's calendar validator in the policy structural + guard, rejecting impossible target dates in manually edited policy blocks. - Completed the Rust facade value-model exports for Core, Target IR, result projections, decoded canonical values, and diagnostic spans, allowing consumers to construct artifact inputs and inspect operation results. diff --git a/docs/topics/release-process/README.md b/docs/topics/release-process/README.md index 1de54d06..a474ab8b 100644 --- a/docs/topics/release-process/README.md +++ b/docs/topics/release-process/README.md @@ -76,7 +76,9 @@ by keeping the total count unchanged. Git tag reconciliation separately checks coverage for every actual tagged release. Required `scope` and `non_goals` values are parsed as TOML string arrays; comments and string contents cannot satisfy field presence. The structural guard and date reconciliation use the -same parsed fields. [RELEASE-REQ-025] +same parsed fields. Policy target dates also use release preparation's calendar +validator, so impossible month-end and leap-day values reject in published, +prep, and planned blocks. [RELEASE-REQ-025] `cargo xtask release-dates` reconciles the dates recorded in the release policy, `CHANGELOG.md`, and `docs/releases/*.md` against the git tags that published diff --git a/docs/topics/release-process/test-plan.md b/docs/topics/release-process/test-plan.md index 45c2e5f5..61ecded1 100644 --- a/docs/topics/release-process/test-plan.md +++ b/docs/topics/release-process/test-plan.md @@ -116,6 +116,8 @@ Out of scope: | RELEASE-TP-031 | implemented | Package versions | RELEASE-REQ-024 | After release preparation, Cargo resolves the facade exact implementation dependency with the requested package versions and an unchanged lockfile. | release_prep_keeps_facade_exact_dependency_resolvable | xtask/src/tests.rs | Offline Cargo metadata over a temporary workspace checks package versions, dependency requirements, and lockfile consistency. | +| RELEASE-TP-032 | implemented | Policy calendar dates | RELEASE-REQ-025 | Policy date validation rejects impossible month-end and leap-day values while accepting real leap days and canonical ordinary dates. | release_policy_dates_require_real_calendar_days | xtask/src/tests.rs, xtask/src/release_prep.rs | The structural guard uses the same calendar judgment as release preparation, including for untagged prep and planned blocks. | + ## Determinism Obligations - Release workflow contract tests inspect checked-in workflow text, not live diff --git a/xtask/src/release_prep.rs b/xtask/src/release_prep.rs index 1bc82fa8..01ed64cc 100644 --- a/xtask/src/release_prep.rs +++ b/xtask/src/release_prep.rs @@ -427,13 +427,13 @@ fn scaffold_release_date( } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ReleasePrepDateError { +pub(crate) enum ReleasePrepDateError { InvalidIsoDate, ClockBeforeEpoch, ClockOutOfRange, } -fn validate_iso_date(date: &str) -> Result<(), ReleasePrepDateError> { +pub(crate) fn validate_iso_date(date: &str) -> Result<(), ReleasePrepDateError> { let bytes = date.as_bytes(); if bytes.len() != 10 || !bytes.iter().enumerate().all(|(index, byte)| { diff --git a/xtask/src/tests.rs b/xtask/src/tests.rs index 4364425b..a5b238b6 100644 --- a/xtask/src/tests.rs +++ b/xtask/src/tests.rs @@ -3309,15 +3309,27 @@ fn annotated_tag(date: &str) -> crate::release_dates::TagRecord { } } +#[test] +fn release_policy_dates_require_real_calendar_days() { + for (value, expected) in [ + ("2026-02-30", false), + ("2026-02-29", false), + ("1900-02-29", false), + ("2026-04-31", false), + ("2026-00-01", false), + ("2026-13-01", false), + ("2026-01-00", false), + ("2026-1-001", false), + ("2026-02-28", true), + ("2000-02-29", true), + ("2024-02-29", true), + ] { + assert_eq!(is_iso_date(value), expected, "{value}"); + } +} + fn is_iso_date(value: &str) -> bool { - let bytes = value.as_bytes(); - bytes.len() == 10 - && bytes[4] == b'-' - && bytes[7] == b'-' - && bytes - .iter() - .enumerate() - .all(|(index, byte)| index == 4 || index == 7 || byte.is_ascii_digit()) + crate::release_prep::validate_iso_date(value).is_ok() } fn wit_named_type(interface: &Interface, name: &str) -> TypeId {