heimdall-consent: README maps doc lines at Heimdall commit 4e99bfa
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (pull_request) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (pull_request) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (pull_request) Has been cancelled
CultNet cross-runtime interop / C# / TypeScript / Rust / Python / Kotlin (pull_request) Has been cancelled
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Has been cancelled
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (pull_request) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (pull_request) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (pull_request) Has been cancelled
CultNet cross-runtime interop / C# / TypeScript / Rust / Python / Kotlin (pull_request) Has been cancelled
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Has been cancelled
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5ab6a6181f
commit
19f77ae3c2
3 changed files with 23 additions and 25 deletions
|
|
@ -3,30 +3,29 @@
|
|||
Typed CultCache records for Heimdall's answer collector: `NoticeAcceptance`, `Answer` and `MemberTerms`,
|
||||
plus the rules that need no store to state (`Answer::is_training_data`, `Answer::redacted`,
|
||||
`validate`). Cut from `docs/answer-collector-consent.md` in GameCult/Heimdall PR #6 at doc commit
|
||||
`2364979`; the doc's tables and Representation section are the contract, and its rulings are on the PR
|
||||
(comment 682). Specification only: no collector, tally, export or tombstone hashing lives here.
|
||||
`4e99bfa`; the doc's tables and Representation section are the contract, and its rulings are on the PR
|
||||
(comments 682 and 759). Specification only: no collector, tally, export or tombstone hashing lives here.
|
||||
|
||||
Entry points: `src/lib.rs`; golden vectors `tests/vectors/consent_golden.csv`, run by `tests/golden.rs`
|
||||
(`cargo test`). Planned, not live: the collector, the tally, the training export.
|
||||
|
||||
## Where each ruling lands (doc lines at 2364979)
|
||||
## Where each ruling lands (doc lines at 4e99bfa)
|
||||
|
||||
| Ruling | Doc | Crate |
|
||||
|---|---|---|
|
||||
| Timestamps are Unix ms UTC, signed 64-bit | L93-94 | `i64` on `accepted_at`, `received_at`, `terms_accepted_at` |
|
||||
| Ids are non-empty opaque strings, `event_id` verbatim; versions unsigned 32-bit and positive | L95-98 | `validate()`; `notice_version: u32`; `check_sample_id` parses `u32` |
|
||||
| Key parts joined with U+001F; empty part or part containing it refused | L99-100 | `key()` |
|
||||
| Tombstone is the exited `Answer` with `text_hash` (32 bytes); HMAC-SHA-256 keyed by a per-member salt held by the member-side service; the collector cannot compute it | L74, L127-147, L186-187 | `Answer.text_hash`; `redacted([u8; 32])` takes the service's output and refuses an answer with no `text` (L143-144); `validate()` requires 32 bytes and `exited` |
|
||||
| A reply with no established notice version or scope is stored with both absent, writes no acceptance, is excluded from every use | L82-89 | `notice_version`/`scope` optional and absent together (`validate()`); vector case `no-scope` |
|
||||
| A reply's version is the one pinned at its `origin_server_ts`; changed version needs a fresh first reply | L32-34, L47, L87-89 | acceptance key includes the version; `is_training_data` accepts only the acceptance for the answer's own version; vector case `version-change` |
|
||||
| Timestamps are Unix ms UTC, signed 64-bit | L98-99; `received_at` is collector-assigned, L75 | `i64` on `accepted_at`, `received_at`, `terms_accepted_at` |
|
||||
| Ids are non-empty opaque strings, `event_id` verbatim; versions unsigned 32-bit and positive | L100-103 | `validate()`; `notice_version: u32`; `check_sample_id` parses `u32` |
|
||||
| Key parts joined with U+001F; empty part or part containing it refused | L104-105 | `key()` |
|
||||
| Tombstone is the exited `Answer` with `text_hash` (32 bytes); HMAC-SHA-256 keyed by a per-member salt held by the member-side service; the collector cannot compute it | L74, L132-152, L186-193 | `Answer.text_hash`; `redacted([u8; 32])` takes the service's output and refuses an answer with no `text` (L148-149); `validate()` requires 32 bytes and `exited` |
|
||||
| A reply with no established notice version or scope is stored with both absent, writes no acceptance, is excluded from every use | L87-94 | `notice_version`/`scope` optional and absent together (`validate()`); vector case `no-scope` |
|
||||
| A reply's version is the one pinned at its `origin_server_ts`; changed version needs a fresh first reply | L32-34, L47, L92-94 | acceptance key includes the version; `is_training_data` accepts only the acceptance for the answer's own version; vector case `version-change` |
|
||||
| `MemberTerms` is the typed shape of the member record's `terms_version`/`terms_accepted_at`, not a second store | L51-56 | `MemberTerms` doc comment; the collector does not hold it |
|
||||
| Training needs acceptance `scope = train` equal to the answer's `scope`, and `accepted_at <= received_at` | L111-119 | `is_training_data` |
|
||||
| One store per scope plus a store for scopeless replies | L120-125 | not modelled; the collector's layout owns it |
|
||||
| Training needs acceptance `scope = train` equal to the answer's `scope`, and `accepted_at <= received_at` | L116-123 | `is_training_data` |
|
||||
| One store per scope plus a store for scopeless replies | L125-130 | not modelled; the collector's layout owns it |
|
||||
|
||||
## Questions for Heimdall
|
||||
Rulings of comment 759 (doc L72, L78-85; no open questions remain):
|
||||
|
||||
1. May `text` be an empty string? The doc says `text` is "string, or absent" (L72) and ids are non-empty
|
||||
(L95), but says nothing of answer text. The crate accepts `Some("")` and `redacted` tombstones it.
|
||||
2. The doc says a later answer to the same key replaces the earlier by "latest `received_at` wins"
|
||||
(L78-80). Equal `received_at` for two different events is unspecified. The crate holds no replace
|
||||
rule; the collector owns it.
|
||||
| Ruling | Doc | Crate |
|
||||
|---|---|---|
|
||||
| A live answer must have non-empty `text` and is rejected otherwise; empty text is valid only on an exited answer (text cleared, hash-only tombstone) | L72, L83-85 | `Answer::validate()` refuses a non-exited answer whose `text` is absent or empty; an exited answer holds no text. Vector cases `empty-live-rejected`, `empty-revoked` |
|
||||
| `received_at` is assigned by the collector; a tie on it breaks by `event_id` compared bytewise, the greater wins as the later reply | L75, L78-81 | `Answer::supersedes`: `(received_at, event_id bytes)` order; the vector harness replaces a stored answer only when the incoming one supersedes it. Vector cases `tie-greater-first`, `tie-greater-last` |
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
//! Typed CultCache records for Heimdall's answer collector, cut from `docs/answer-collector-consent.md`
|
||||
//! in GameCult/Heimdall PR #6 (doc commit 2364979): the notice acceptance record, the answer record,
|
||||
//! in GameCult/Heimdall PR #6 (doc commit 4e99bfa): the notice acceptance record, the answer record,
|
||||
//! and the typed shape of the terms pair on the member record. Field names and types follow the
|
||||
//! doc's tables and its Representation section; README.md cites the doc lines. Anything the doc
|
||||
//! still leaves open is listed there as a question for Heimdall.
|
||||
//! doc's tables and its Representation section; README.md cites the doc lines.
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use cultcache_rs::DatabaseEntry;
|
||||
|
|
@ -108,8 +107,8 @@ pub struct Answer {
|
|||
/// One-way: set by redaction, never cleared.
|
||||
#[cultcache(key = 6, default)]
|
||||
pub exited: bool,
|
||||
/// Assigned by the collector on receipt, unix milliseconds UTC (the reply's `origin_server_ts`
|
||||
/// in practice); ties are broken by `supersedes`.
|
||||
/// Assigned by the collector on receipt, unix milliseconds UTC, not copied from the reply (doc L75);
|
||||
/// ties are broken by `supersedes`.
|
||||
#[cultcache(key = 7)]
|
||||
pub received_at: i64,
|
||||
#[cultcache(key = 8)]
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
# Ops run in order against a real CultCache; each case starts empty. Timestamps are unix ms.
|
||||
# reply: first reply under a (room, member, notice_version) writes an acceptance, then the answer replaces any earlier one at (room, sample_id, member); payload is the text.
|
||||
# answer_raw: stores the answer with no acceptance written. redact: replaces the answer whose event_id matches with its tombstone; payload is the 32-byte tombstone hash as 64 hex chars (the member-side service's output; the crate never computes it).
|
||||
# A reply with no established notice version or scope is stored with both absent (doc L82-89, Answer record), writes no acceptance, and is excluded. answer_raw with a version but no acceptance models a lost or never-written acceptance: still excluded (doc L120).
|
||||
# A reply with no established notice version or scope is stored with both absent (doc L87-94, Answer record), writes no acceptance, and is excluded. answer_raw with a version but no acceptance models a lost or never-written acceptance: still excluded (doc L119-122).
|
||||
# reply_rejected: validate must refuse the answer (nothing stored). answer_revoked: stores an exited answer with no text; payload is the tombstone hash (empty means none). expect_winner: the stored answer's event_id and text must equal event and payload.
|
||||
# Replacement at one key: the later reply is the greater (received_at, event_id), event_id compared bytewise; input order never decides (doc L78-80 and Heimdall PR #6 comment 759).
|
||||
# Replacement at one key: the later reply is the greater (received_at, event_id), event_id compared bytewise; input order never decides (doc L78-81 and Heimdall PR #6 comment 759).
|
||||
# terms: member field terms_version (payload) and terms_accepted_at (ts). expect_*: payload is what must hold.
|
||||
# Expected values are written by hand from docs/answer-collector-consent.md (Heimdall PR #6, doc commit 2364979), not computed by the crate.
|
||||
# Expected values are written by hand from docs/answer-collector-consent.md (Heimdall PR #6, doc commit 4e99bfa), not computed by the crate.
|
||||
versioned-key,reply,m1,room1,1,train,s1@1,first,100,e1
|
||||
versioned-key,reply,m1,room1,1,train,s1@2,second,200,e2
|
||||
versioned-key,reply,m1,room1,1,train,s1@1,replaced,300,e3
|
||||
|
|
|
|||
|
Loading…
Reference in a new issue