feat: complete Psyche W2 contract foundation - #4
Merged
Conversation
- Add workspace members: psyche-store, psyche-coven, psyche-surfaces, psyche-test-support with minimal src/lib.rs and inherited manifest fields. - psyche-test-support sets publish = false directly; the other three use publish.workspace = true. - Add workspace dependencies for new crates plus async-trait, rusqlite, serde_json_canonicalizer, sha2, time, ulid, proptest. - Add scripts/check-g2-workspace.sh to verify all four crates are present in cargo metadata and that psyche-test-support.publish == [] and no manifest mixes publish.workspace with a bare publish key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add contracts::RecordKind (15 kinds, stable 4-char prefixes: ids_, int_,
grf_, nod_, att_, dlg_, bud_, apr_, evd_, vrd_, rcv_, adn_, sev_, sfx_,
del_) and contracts::SchemaKind (16 kinds incl. Error), with
SchemaKind::record_kind() as the sole SchemaKind -> RecordKind mapping.
ExecutionBinding maps to Attempt; Error maps to None. No RecordKind
variant exists for ExecutionBinding, keeping RecordKind::ALL at 15.
- Add contracts::SchemaVersion { kind, major }, parsed from and displayed
as psyche.<kind>.v<major>, serde try_from/into String. Accepts exactly
the 16 canonical major-1 strings the registry defines; an unrecognised
kind is ContractError::UnknownSchema, a recognised kind at any other
(or malformed) major is ContractError::UnsupportedMajor.
- Add id::RecordId: opaque newtype validated as <RecordKind prefix><26
char canonical uppercase ULID>, with RecordId::parse (exact requested
kind) and RecordId::parse_any/TryFrom<String> (kind derived from the
matched prefix) for serde. Rejects wrong-kind prefixes (e.g. dly_ where
Delivery requires del_, or del_ where Delegation requires dlg_),
lowercase or non-canonical ULID characters, and trailing data.
- Add id::RequestId: separate req_-prefixed newtype, not a RecordKind,
with the same canonical-ULID validation.
- Add digest::canonical_bytes (delegates to serde_json_canonicalizer, so
object key order does not affect the output) and digest::digest,
returning a validated digest::Sha256Digest (sha256: + 64 lowercase hex
chars, serde try_from/into String, rejects wrong prefix, wrong length,
uppercase hex, and trailing content).
- Add contracts::ContractError covering schema, record/request id, digest,
and canonicalization validation failures.
- Document schema.rs's existing CONFIG_SCHEMA_VERSION as a deliberately
separate authority from contracts::SchemaVersion (config-file loading
vs. the domain contract registry) rather than integrating it, since
psyche.config.v1 is not one of the registry's 16 defined kinds.
- Records, CanonicalDocument, decode_document, store validation, and
QuarantineId are explicitly out of scope for this task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the lossy serde_value traversal with a type-preserving Serde validation pass that covers numeric keys, i128/u128, every compound branch, and non-finite floats before canonicalizing the original value. Redact schema and serializer-controlled error text, and add boundary, collision, traversal, and near-1MiB input regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Defect 1 — map keys bypass safe-number validation:
validate_serialized_domain (serde_value era) iterated only Map values,
letting unsafe u64 keys like 9007199254740992 and 9007199254740993
reach serde_json_canonicalizer unchecked; those two values round through
f64 to the same 9007199254740992.0, silently collapsing two distinct
entries to one JSON key. The DomainValidator serializer (landed in
a74266f) already calls serialize_key on every map key, ensuring the
same validate_unsigned / validate_signed guard that applies to values
is applied to keys. This commit adds the missing TDD evidence:
- map_key_collision_pair_rejected_before_f64_canonicalization: both
unsafe keys together in one BTreeMap<u64,_> must return
NonInteroperableNumber before canonicalization.
- map_key_u64_boundary_accepted_and_one_over_rejected: MAX_SAFE_INTEGER
as a u64 key is accepted; MAX_SAFE_INTEGER+1 is rejected.
- nested_map_unsafe_u64_keys_are_rejected: an unsafe u64 key nested
inside a struct field is also rejected; a safe key is accepted.
Defect 2 — serializer error text leaks:
canonicalization_failed(error) previously stored error.to_string() in
ContractError::CanonicalizationFailed { reason }, leaking arbitrary
custom-serializer messages into Debug/Display. CanonicalizationFailed
is now a payload-free unit variant (a74266f); canonicalization_failed
discards the error argument. The existing test
canonicalization_errors_do_not_retain_custom_serializer_messages covers
both defects: it injects a 900 000-char sentinel and asserts that
Debug/Display exclude it and remain under 256 bytes.
No other crates match on CanonicalizationFailed { reason }.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
decode_document ran numeric interoperable-domain validation (validate_json_domain) before the schema_version probe and typed enum prevalidation. An unsupported-major or unknown-enum document that also contained an unsafe number was misclassified as NonInteroperableNumber instead of surfacing UnsupportedMajor, UnknownSchema, or UnknownEnumValue as Task 4 requires. Reorder decode_document so classification runs strictly in the required precedence: recursive syntactic parse (duplicate-key and nesting-depth rejection) -> schema_version probe / SchemaVersion classification -> recognized schema's typed enum prevalidation -> numeric domain validation -> typed deserialization / CanonicalDocument validation. Add mixed-error regression tests covering unsupported major, unknown schema, and unknown enum value each paired with a nested unsafe integer, confirm known-good documents still reject unsafe nested integers, and confirm duplicate keys are rejected ahead of an unsupported major. All errors stay redacted with no raw value retained. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Route embedded JSON values through the strict duplicate-aware deserializer so direct typed decoding cannot erase conflicting object fields before contract validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the reviewed read-only recovery preflight, private storage boundary, and complete migration rollback fixtures after the concurrent persistence work removed them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exercise every cancellation correlation, evidence, reason, identifier, and window mutation from valid baselines and prove direct insert leaves no durable state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes the approved W2/G2 foundation plan: typed canonical records, forward-only SQLite migrations, immutable storage, quarantine/retention, behavior-level fake ports, and migration/property/crash evidence. Includes maintainer-approved Rust 1.88 MSRV and fail-closed local-storage/schema authentication. No W3-W9 behavior and no G4+ capability is enabled.
Closes #3.