Stamp every knowledge row with its own acquisition revision (PA.f44)
The recency marker was `minted_at > last_acted_at`, and minted_at is set only by a speech or a display event's own fan-out. A row AcquireKnowledge lands, a row Witness lands, and a row declaration-time seeding lands all carry no minted_at, so `new` computed with `>` a `None` was always false for them: a fact landed by a patch, a seed, or a ruling could never surface to a Persona as new, however long after the subject's last act it arrived. Knowledge now carries acquired_at: u64, the revision this write's own commit resolves to (matching how DecisionEvent.revision and the minted_at scan built from it are computed: one past the pre-increment state.revision apply runs against). Every writer of the knowledge map sets it: AcquireKnowledge, Communicate's fan-out, Display's fan-out, and Witness. The marker becomes `acquired_at > last_acted_at`, with a subject that never acted (last_acted_at is None) seeing every row it holds as new outright, matching the ruling exactly rather than leaning on Option ordering. minted_at keeps its own meaning — which act minted a spoken or displayed claim — and stays on KnowledgeSnapshot and on the wire; no schema bump, since no consumer.v6 store exists yet. AcquireKnowledge's own no-op refusal now compares confidence and source alone, not the whole row, so a later acquired_at cannot make an identical re-assertion stop refusing as "knowledge operation changes nothing". Tests: a row AcquireKnowledge lands after the subject's last act is new; one that lands before it is not; the acquisition revision survives a journal reopen; and the Cut 0 stimulus test's third row — seeded with no minted_at, previously "new": false — now reads "new": true, because its subject never acted and PA.f44 says every row such a subject holds is new. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
14d85dac59
commit
3b1f412b92
3 changed files with 344 additions and 29 deletions
|
|
@ -4852,9 +4852,15 @@ impl SelectedDecision {
|
|||
};
|
||||
// The recency marker: strictly after the subject's own last
|
||||
// exercised decision, so a subject that never acted
|
||||
// (`last_acted_at` is `None`) sees every minted row as new,
|
||||
// (`last_acted_at` is `None`) sees every row it holds as new,
|
||||
// and the subject's own acting revision is not itself new.
|
||||
let new = entry.minted_at > self.subject.last_acted_at;
|
||||
// `acquired_at` is written by every knowledge writer (PA.f44),
|
||||
// unlike `minted_at`, which a patch, a seed, or a ruling never
|
||||
// sets, so this marker sees rows those writers land too.
|
||||
let new = match self.subject.last_acted_at {
|
||||
None => true,
|
||||
Some(acted_at) => entry.acquired_at > acted_at,
|
||||
};
|
||||
json!({
|
||||
"how": how,
|
||||
"by": by,
|
||||
|
|
@ -7003,6 +7009,7 @@ mod tests {
|
|||
via: None,
|
||||
},
|
||||
minted_at: Some(40),
|
||||
acquired_at: 40,
|
||||
},
|
||||
// Mara also saw Iris do something. The row is Iris's own
|
||||
// sentence, marked as seen and never as said.
|
||||
|
|
@ -7013,6 +7020,7 @@ mod tests {
|
|||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Seen { by: speaker_id },
|
||||
minted_at: Some(41),
|
||||
acquired_at: 41,
|
||||
},
|
||||
],
|
||||
commitments: Vec::new(),
|
||||
|
|
@ -7042,6 +7050,7 @@ mod tests {
|
|||
confidence: Confidence::Certain,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
minted_at: None,
|
||||
acquired_at: 0,
|
||||
}],
|
||||
commitments: Vec::new(),
|
||||
pressures: Vec::new(),
|
||||
|
|
@ -7616,6 +7625,7 @@ mod tests {
|
|||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
minted_at: Some(1),
|
||||
acquired_at: 1,
|
||||
},
|
||||
KnowledgeSnapshot {
|
||||
fact: EntityId::issue(),
|
||||
|
|
@ -7624,6 +7634,7 @@ mod tests {
|
|||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
minted_at: Some(2),
|
||||
acquired_at: 2,
|
||||
},
|
||||
KnowledgeSnapshot {
|
||||
fact: EntityId::issue(),
|
||||
|
|
@ -7632,6 +7643,7 @@ mod tests {
|
|||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
minted_at: Some(3),
|
||||
acquired_at: 3,
|
||||
},
|
||||
];
|
||||
let boundary_selected = SelectedDecision {
|
||||
|
|
@ -7732,6 +7744,242 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// PA.f44: a row `AcquireKnowledge` lands — a patch, not a minting speech
|
||||
/// or display act — after the subject's own last act is new. Before
|
||||
/// PA.f44 this row carried no `minted_at` at all, so the old marker
|
||||
/// (`minted_at > last_acted_at`, `None` never beats `Some`) could never
|
||||
/// flag it, whatever revision it actually landed on.
|
||||
#[test]
|
||||
fn a_row_acquired_by_a_patch_after_the_last_act_is_new() {
|
||||
use crate::tests::{
|
||||
FLOOD_STATEMENT, auth_principal, command, operations, opportunity_for, owner,
|
||||
speech_world, submit_owner,
|
||||
};
|
||||
use crate::{
|
||||
AuthenticatedCaller, AuthoredSource, DecisionInvocation, Ref, Role, RoleBinding,
|
||||
Statement, SubmitReceipt, Target, WorldKernel,
|
||||
};
|
||||
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let mut kernel = WorldKernel::create(
|
||||
directory.path().join("world.cc"),
|
||||
crate::tests::creation(CommandId::new(), "PatchAfterAct"),
|
||||
&auth_principal(owner()),
|
||||
)
|
||||
.expect("a created world")
|
||||
.0;
|
||||
let (speech, active) = speech_world(&mut kernel);
|
||||
|
||||
// The listener acts once, so it carries a `last_acted_at`.
|
||||
let opportunity = opportunity_for(&active, speech.listener);
|
||||
let caller = CallerId::Controller(opportunity.controller_id);
|
||||
let receipt = kernel
|
||||
.submit(
|
||||
command(
|
||||
&active,
|
||||
CommandId::new(),
|
||||
caller.clone(),
|
||||
CommandBody::ExerciseDecision {
|
||||
opportunity,
|
||||
invocation: DecisionInvocation {
|
||||
affordance: speech.whisper,
|
||||
bindings: vec![RoleBinding {
|
||||
role: Role("target".into()),
|
||||
target: Target::Subject(speech.speaker),
|
||||
}],
|
||||
proposed: Vec::new(),
|
||||
speech: Some(Statement::new("I have something to say.").unwrap()),
|
||||
display: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
&AuthenticatedCaller::fixture(caller),
|
||||
)
|
||||
.expect("the whisper commits");
|
||||
assert!(matches!(receipt, SubmitReceipt::Applied(_)));
|
||||
let after_act = kernel.snapshot().unwrap();
|
||||
|
||||
// A patch, after the act, lands a row the listener never spoke or
|
||||
// saw: the sole scenario PA.f44 exists for.
|
||||
submit_owner(
|
||||
&mut kernel,
|
||||
&after_act,
|
||||
operations(vec![crate::patch::ComponentOp::AcquireKnowledge {
|
||||
subject: Ref::Existing(speech.listener),
|
||||
fact: Ref::Existing(speech.flood),
|
||||
source: AuthoredSource::Witnessed,
|
||||
confidence: Confidence::Believed,
|
||||
}]),
|
||||
);
|
||||
let after_patch = kernel.snapshot().unwrap();
|
||||
|
||||
let subject = after_patch
|
||||
.subjects
|
||||
.iter()
|
||||
.find(|subject| subject.id == speech.listener)
|
||||
.unwrap()
|
||||
.clone();
|
||||
assert!(subject.last_acted_at.is_some());
|
||||
let row = subject
|
||||
.knowledge
|
||||
.iter()
|
||||
.find(|row| row.fact == speech.flood)
|
||||
.expect("the patch landed the row");
|
||||
assert_eq!(row.minted_at, None, "a patch mints no event");
|
||||
let opportunity = opportunity_for(&after_patch, speech.listener);
|
||||
let granted: Vec<AffordanceSnapshot> = after_patch
|
||||
.affordances
|
||||
.iter()
|
||||
.filter(|entry| subject.affordances.contains(&entry.id))
|
||||
.cloned()
|
||||
.collect();
|
||||
let selected = SelectedDecision {
|
||||
snapshot: after_patch,
|
||||
subject,
|
||||
opportunity,
|
||||
granted,
|
||||
};
|
||||
let rendered = selected
|
||||
.projector_knowledge()
|
||||
.into_iter()
|
||||
.find(|row| row["text"] == Value::String(FLOOD_STATEMENT.into()))
|
||||
.expect("the flood row is rendered");
|
||||
assert_eq!(rendered["new"], Value::Bool(true));
|
||||
}
|
||||
|
||||
/// PA.f44's other boundary: a row `AcquireKnowledge` lands *before* the
|
||||
/// subject's own last act is not new, exactly as a row minted by an act
|
||||
/// before it is not.
|
||||
#[test]
|
||||
fn a_row_acquired_by_a_patch_before_the_last_act_is_not_new() {
|
||||
use crate::tests::{
|
||||
FLOOD_STATEMENT, auth_principal, command, operations, opportunity_for, owner,
|
||||
speech_world, submit_owner,
|
||||
};
|
||||
use crate::{
|
||||
AuthenticatedCaller, AuthoredSource, DecisionInvocation, Ref, Role, RoleBinding,
|
||||
Statement, SubmitReceipt, Target, WorldKernel,
|
||||
};
|
||||
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let mut kernel = WorldKernel::create(
|
||||
directory.path().join("world.cc"),
|
||||
crate::tests::creation(CommandId::new(), "PatchBeforeAct"),
|
||||
&auth_principal(owner()),
|
||||
)
|
||||
.expect("a created world")
|
||||
.0;
|
||||
let (speech, active) = speech_world(&mut kernel);
|
||||
|
||||
// The patch lands first, before the listener has ever acted.
|
||||
submit_owner(
|
||||
&mut kernel,
|
||||
&active,
|
||||
operations(vec![crate::patch::ComponentOp::AcquireKnowledge {
|
||||
subject: Ref::Existing(speech.listener),
|
||||
fact: Ref::Existing(speech.flood),
|
||||
source: AuthoredSource::Witnessed,
|
||||
confidence: Confidence::Believed,
|
||||
}]),
|
||||
);
|
||||
let after_patch = kernel.snapshot().unwrap();
|
||||
|
||||
// Now the listener acts, so its `last_acted_at` moves past the patch.
|
||||
let opportunity = opportunity_for(&after_patch, speech.listener);
|
||||
let caller = CallerId::Controller(opportunity.controller_id);
|
||||
let receipt = kernel
|
||||
.submit(
|
||||
command(
|
||||
&after_patch,
|
||||
CommandId::new(),
|
||||
caller.clone(),
|
||||
CommandBody::ExerciseDecision {
|
||||
opportunity,
|
||||
invocation: DecisionInvocation {
|
||||
affordance: speech.whisper,
|
||||
bindings: vec![RoleBinding {
|
||||
role: Role("target".into()),
|
||||
target: Target::Subject(speech.speaker),
|
||||
}],
|
||||
proposed: Vec::new(),
|
||||
speech: Some(Statement::new("I have something to say.").unwrap()),
|
||||
display: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
&AuthenticatedCaller::fixture(caller),
|
||||
)
|
||||
.expect("the whisper commits");
|
||||
assert!(matches!(receipt, SubmitReceipt::Applied(_)));
|
||||
let after_act = kernel.snapshot().unwrap();
|
||||
|
||||
let subject = after_act
|
||||
.subjects
|
||||
.iter()
|
||||
.find(|subject| subject.id == speech.listener)
|
||||
.unwrap()
|
||||
.clone();
|
||||
assert!(subject.last_acted_at.is_some());
|
||||
let opportunity = opportunity_for(&after_act, speech.listener);
|
||||
let granted: Vec<AffordanceSnapshot> = after_act
|
||||
.affordances
|
||||
.iter()
|
||||
.filter(|entry| subject.affordances.contains(&entry.id))
|
||||
.cloned()
|
||||
.collect();
|
||||
let selected = SelectedDecision {
|
||||
snapshot: after_act,
|
||||
subject,
|
||||
opportunity,
|
||||
granted,
|
||||
};
|
||||
let rendered = selected
|
||||
.projector_knowledge()
|
||||
.into_iter()
|
||||
.find(|row| row["text"] == Value::String(FLOOD_STATEMENT.into()))
|
||||
.expect("the flood row is rendered");
|
||||
assert_eq!(rendered["new"], Value::Bool(false));
|
||||
}
|
||||
|
||||
/// PA.f44: `acquired_at` is real committed state, not a snapshot-only
|
||||
/// derivation, so it must survive a journal reopen exactly as
|
||||
/// `minted_at`'s source events do.
|
||||
#[test]
|
||||
fn the_acquisition_revision_survives_replay() {
|
||||
use crate::tests::{auth_principal, operations, owner, speech_world};
|
||||
use crate::{AuthoredSource, Ref, WorldKernel};
|
||||
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let mut kernel = WorldKernel::create(
|
||||
directory.path().join("world.cc"),
|
||||
crate::tests::creation(CommandId::new(), "AcquisitionReplay"),
|
||||
&auth_principal(owner()),
|
||||
)
|
||||
.expect("a created world")
|
||||
.0;
|
||||
let (speech, active) = speech_world(&mut kernel);
|
||||
crate::tests::submit_owner(
|
||||
&mut kernel,
|
||||
&active,
|
||||
operations(vec![crate::patch::ComponentOp::AcquireKnowledge {
|
||||
subject: Ref::Existing(speech.listener),
|
||||
fact: Ref::Existing(speech.flood),
|
||||
source: AuthoredSource::Witnessed,
|
||||
confidence: Confidence::Believed,
|
||||
}]),
|
||||
);
|
||||
let acquired_at = kernel.state.knowledge[&speech.listener][&speech.flood].acquired_at;
|
||||
let world_id = kernel.state.world_id;
|
||||
drop(kernel);
|
||||
|
||||
let reopened =
|
||||
WorldKernel::open(directory.path().join("world.cc"), world_id).expect("it replays");
|
||||
assert_eq!(
|
||||
reopened.state.knowledge[&speech.listener][&speech.flood].acquired_at,
|
||||
acquired_at
|
||||
);
|
||||
}
|
||||
|
||||
/// Verification: `turn` refuses any mode but `NarrativePersona`, and
|
||||
/// `narrate` refuses any mode but `Human` — before either ever reaches the
|
||||
/// mailbox.
|
||||
|
|
@ -7936,9 +8184,14 @@ mod tests {
|
|||
/// PA-Q2's default plus Cut 6: the stimulus is exactly Cut 0's capture
|
||||
/// (session scratchpad `play-cut0/base-stimulus.txt`: one `Told` row, one
|
||||
/// `Seen` row, one held fact with no author) with exactly one `"new"`
|
||||
/// field added per row. This subject never acted, so both minted rows are
|
||||
/// new; the third row was never minted by an event at all (no
|
||||
/// `minted_at`), so it carries no arrival time to compare and is not new.
|
||||
/// field added per row. This subject never acted (`last_acted_at` is
|
||||
/// `None`), so every row it holds is new (PA.f44) — including the third,
|
||||
/// which carries no `minted_at` at all (no minting event ever asserted
|
||||
/// it) but still carries `acquired_at`, the field every knowledge writer
|
||||
/// sets. Before PA.f44 that row read `"new": false`: a row with no
|
||||
/// `minted_at` could never be new, which is exactly the bug — a fact
|
||||
/// landed by a patch, a seed, or a ruling would never surface to a
|
||||
/// Persona as new.
|
||||
#[test]
|
||||
fn the_stimulus_equals_cut_zeros_capture_plus_the_marker() {
|
||||
let subject_id = SubjectId::issue();
|
||||
|
|
@ -7979,6 +8232,7 @@ mod tests {
|
|||
via: None,
|
||||
},
|
||||
minted_at: Some(1),
|
||||
acquired_at: 1,
|
||||
},
|
||||
KnowledgeSnapshot {
|
||||
fact: EntityId::issue(),
|
||||
|
|
@ -7987,6 +8241,7 @@ mod tests {
|
|||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Seen { by: speaker_id },
|
||||
minted_at: Some(2),
|
||||
acquired_at: 2,
|
||||
},
|
||||
KnowledgeSnapshot {
|
||||
fact: EntityId::issue(),
|
||||
|
|
@ -7995,6 +8250,9 @@ mod tests {
|
|||
confidence: Confidence::Certain,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
minted_at: None,
|
||||
// No minting event ever asserted this row (declaration-time
|
||||
// seeding, PA.f44) but it still carries `acquired_at`.
|
||||
acquired_at: 0,
|
||||
},
|
||||
],
|
||||
commitments: Vec::new(),
|
||||
|
|
@ -8073,7 +8331,7 @@ mod tests {
|
|||
"by": null,
|
||||
"certainty": "certain",
|
||||
"how": "known",
|
||||
"new": false,
|
||||
"new": true,
|
||||
"text": "The rain gate sticks in the cold."
|
||||
}
|
||||
]))
|
||||
|
|
|
|||
|
|
@ -1074,6 +1074,10 @@ pub(crate) struct KnowledgeSnapshot {
|
|||
/// The revision of the act that minted this claim, spoken or displayed,
|
||||
/// when some committed event asserted it.
|
||||
pub(crate) minted_at: Option<u64>,
|
||||
/// The revision at which this subject acquired this row, from `Knowledge`
|
||||
/// directly: always present, unlike `minted_at`, so the recency marker can
|
||||
/// see a row a patch, a seed, or a ruling landed with no minting event.
|
||||
pub(crate) acquired_at: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
|
@ -2718,23 +2722,38 @@ fn apply_operation(
|
|||
"knowledge operation names no live subject, or evidences a claim".into(),
|
||||
));
|
||||
}
|
||||
let entry = Knowledge {
|
||||
confidence: *confidence,
|
||||
source: match source {
|
||||
AuthoredSource::Witnessed => KnowledgeSource::Witnessed,
|
||||
AuthoredSource::Evidenced => KnowledgeSource::Evidenced,
|
||||
},
|
||||
let source = match source {
|
||||
AuthoredSource::Witnessed => KnowledgeSource::Witnessed,
|
||||
AuthoredSource::Evidenced => KnowledgeSource::Evidenced,
|
||||
};
|
||||
// Compared on `confidence` and `source` alone: `acquired_at` is
|
||||
// this write's own revision, not part of what the author asserts,
|
||||
// so a re-assertion identical in everything the author controls
|
||||
// still refuses as a no-op even though it would land a later
|
||||
// `acquired_at`.
|
||||
if state
|
||||
.knowledge
|
||||
.get(subject)
|
||||
.and_then(|held| held.get(fact))
|
||||
.is_some_and(|held| *held == entry)
|
||||
.is_some_and(|held| held.confidence == *confidence && held.source == source)
|
||||
{
|
||||
return Err(KernelError::Invariant(
|
||||
"knowledge operation changes nothing".into(),
|
||||
));
|
||||
}
|
||||
// Matches `DecisionEvent.revision` and the `minted_at` scan built
|
||||
// from it (`action::exercise`, `snapshot`): both name the revision
|
||||
// this commit resolves to, one past `state.revision` here, which
|
||||
// still holds the *previous* revision until `submit` bumps it.
|
||||
let acquired_at = state
|
||||
.revision
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| KernelError::Serialization("world revision overflow".into()))?;
|
||||
let entry = Knowledge {
|
||||
confidence: *confidence,
|
||||
source,
|
||||
acquired_at,
|
||||
};
|
||||
state
|
||||
.knowledge
|
||||
.entry(*subject)
|
||||
|
|
@ -2773,6 +2792,12 @@ fn apply_operation(
|
|||
// An empty fan-out is legal: the delta of a telling is a property of
|
||||
// the world — an empty room, a silenced channel — not a defect in
|
||||
// the proposal. Speaking alone commits the claim and lands nothing.
|
||||
// `acquired_at` matches `minted_at`: one past `state.revision`, the
|
||||
// revision this commit resolves to (PA.f44).
|
||||
let acquired_at = state
|
||||
.revision
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| KernelError::Serialization("world revision overflow".into()))?;
|
||||
for listener in fan_out(state, *speaker, *fact, to) {
|
||||
state.knowledge.entry(listener).or_default().insert(
|
||||
*fact,
|
||||
|
|
@ -2782,6 +2807,7 @@ fn apply_operation(
|
|||
by: *speaker,
|
||||
via: to.channel(),
|
||||
},
|
||||
acquired_at,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -2804,12 +2830,17 @@ fn apply_operation(
|
|||
"an actor displays from no place".into(),
|
||||
));
|
||||
}
|
||||
let acquired_at = state
|
||||
.revision
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| KernelError::Serialization("world revision overflow".into()))?;
|
||||
for viewer in fan_out(state, *actor, *fact, &room) {
|
||||
state.knowledge.entry(viewer).or_default().insert(
|
||||
*fact,
|
||||
Knowledge {
|
||||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Seen { by: *actor },
|
||||
acquired_at,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -2840,12 +2871,17 @@ fn apply_operation(
|
|||
"a witness reaches nobody who does not already hold the fact".into(),
|
||||
));
|
||||
}
|
||||
let acquired_at = state
|
||||
.revision
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| KernelError::Serialization("world revision overflow".into()))?;
|
||||
for witness in recipients {
|
||||
state.knowledge.entry(witness).or_default().insert(
|
||||
*fact,
|
||||
Knowledge {
|
||||
confidence: *confidence,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
acquired_at,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -3845,6 +3881,7 @@ fn snapshot(state: &WorldState) -> Result<WorldSnapshot, KernelError> {
|
|||
confidence: held.confidence,
|
||||
source: held.source,
|
||||
minted_at: minted_at.get(fact).copied(),
|
||||
acquired_at: held.acquired_at,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, KernelError>>()?;
|
||||
|
|
@ -9752,6 +9789,7 @@ mod knowledge_tests {
|
|||
by: speech.speaker,
|
||||
via: None,
|
||||
},
|
||||
acquired_at: active.revision + 1,
|
||||
})
|
||||
);
|
||||
// The yard is inside the hall, but a voice fills a room and not the
|
||||
|
|
@ -9922,6 +9960,7 @@ mod knowledge_tests {
|
|||
Some(Knowledge {
|
||||
confidence: Confidence::Believed,
|
||||
source: KnowledgeSource::Seen { by: speech.speaker },
|
||||
acquired_at: active.revision + 1,
|
||||
})
|
||||
);
|
||||
for blind in [speech.bystander, speech.stranger, speech.speaker] {
|
||||
|
|
@ -10260,6 +10299,7 @@ mod knowledge_tests {
|
|||
by: speech.speaker,
|
||||
via: Some(speech.horn),
|
||||
},
|
||||
acquired_at: active.revision + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -10779,6 +10819,7 @@ mod soul_knowledge_tests {
|
|||
by: speech.speaker,
|
||||
via: Some(speech.horn),
|
||||
},
|
||||
acquired_at: active.revision + 1,
|
||||
})
|
||||
);
|
||||
// The controller gains no knowledge of its own claim: it is outside
|
||||
|
|
@ -11051,6 +11092,7 @@ mod soul_knowledge_tests {
|
|||
by: speech.speaker,
|
||||
via: None,
|
||||
},
|
||||
acquired_at: active.revision + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -11504,10 +11546,11 @@ mod witness_tests {
|
|||
.copied()
|
||||
}
|
||||
|
||||
fn seen(confidence: Confidence) -> Option<Knowledge> {
|
||||
fn seen(confidence: Confidence, acquired_at: u64) -> Option<Knowledge> {
|
||||
Some(Knowledge {
|
||||
confidence,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
acquired_at,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -11528,11 +11571,11 @@ mod witness_tests {
|
|||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.villager, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, active.revision + 1)
|
||||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.neighbour, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, active.revision + 1)
|
||||
);
|
||||
assert_eq!(knows(&kernel, world.coaster, world.asteroid), None);
|
||||
assert_eq!(knows(&kernel, world.drifter, world.asteroid), None);
|
||||
|
|
@ -11555,7 +11598,7 @@ mod witness_tests {
|
|||
] {
|
||||
assert_eq!(
|
||||
knows(&kernel, subject, world.moon),
|
||||
seen(Confidence::Certain)
|
||||
seen(Confidence::Certain, after.revision + 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -11586,8 +11629,10 @@ mod witness_tests {
|
|||
by: world.coaster,
|
||||
via: None,
|
||||
},
|
||||
acquired_at: 0,
|
||||
};
|
||||
let mut state = kernel.state.clone();
|
||||
let state_revision = state.revision;
|
||||
state
|
||||
.knowledge
|
||||
.entry(world.villager)
|
||||
|
|
@ -11609,6 +11654,7 @@ mod witness_tests {
|
|||
Knowledge {
|
||||
confidence: Confidence::Certain,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
acquired_at: state_revision + 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -11738,7 +11784,7 @@ mod witness_tests {
|
|||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.coaster, world.asteroid),
|
||||
seen(Confidence::Certain)
|
||||
seen(Confidence::Certain, seeded.revision + 1)
|
||||
);
|
||||
|
||||
// The inverse: the coast's only stander leaves before the witness, so
|
||||
|
|
@ -11831,6 +11877,7 @@ mod witness_tests {
|
|||
site: Site::Operation(0)
|
||||
}]
|
||||
);
|
||||
let before_admit = kernel.state.revision;
|
||||
let admitted = submit(&mut kernel, world.village);
|
||||
assert!(
|
||||
matches!(admitted, Ok(SubmitReceipt::Applied(_))),
|
||||
|
|
@ -11838,7 +11885,7 @@ mod witness_tests {
|
|||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.villager, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, before_admit + 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -12080,11 +12127,11 @@ mod witness_tests {
|
|||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.villager, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, active.revision + 1)
|
||||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.neighbour, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, active.revision + 1)
|
||||
);
|
||||
assert_eq!(knows(&kernel, world.coaster, world.asteroid), None);
|
||||
|
||||
|
|
@ -12130,7 +12177,7 @@ mod witness_tests {
|
|||
assert_eq!(reopened.state, committed);
|
||||
assert_eq!(
|
||||
knows(&reopened, world.villager, world.asteroid),
|
||||
seen(Confidence::Certain)
|
||||
seen(Confidence::Certain, active.revision + 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -12152,6 +12199,7 @@ mod witness_tests {
|
|||
Knowledge {
|
||||
confidence: Confidence::Doubted,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
acquired_at: 0,
|
||||
},
|
||||
);
|
||||
for root in [world.hemisphere, world.region, world.village, world.coast] {
|
||||
|
|
@ -12227,6 +12275,7 @@ mod witness_tests {
|
|||
// reach it. Presence is read from `positions` alone: there is no second
|
||||
// notion of being somewhere for a placeless subject to fall back on.
|
||||
let mut adrift = kernel.state.clone();
|
||||
let adrift_revision = adrift.revision;
|
||||
adrift.positions.remove(&world.drifter);
|
||||
for root in [
|
||||
world.hemisphere,
|
||||
|
|
@ -12263,6 +12312,7 @@ mod witness_tests {
|
|||
Knowledge {
|
||||
confidence: Confidence::Certain,
|
||||
source: KnowledgeSource::Witnessed,
|
||||
acquired_at: adrift_revision + 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -12364,6 +12414,7 @@ mod witness_tests {
|
|||
// Declared and placed under the witnessed place: the resolver's
|
||||
// candidate map carries the draft position, and the apply pass derives
|
||||
// the same subject from live state.
|
||||
let before_arrival = kernel.state.revision;
|
||||
let receipt = arrival(
|
||||
&mut kernel,
|
||||
"arrival",
|
||||
|
|
@ -12383,17 +12434,17 @@ mod witness_tests {
|
|||
.0;
|
||||
assert_eq!(
|
||||
knows(&kernel, arrived, world.asteroid),
|
||||
seen(Confidence::Certain)
|
||||
seen(Confidence::Certain, before_arrival + 1)
|
||||
);
|
||||
// The standers who already held it kept their own rows, so the landing
|
||||
// was exactly the draft subject.
|
||||
// The standers who already held it kept their own rows, from the
|
||||
// earlier witness, and this landing did not touch them.
|
||||
assert_eq!(
|
||||
knows(&kernel, world.villager, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, active.revision + 1)
|
||||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.neighbour, world.asteroid),
|
||||
seen(Confidence::Believed)
|
||||
seen(Confidence::Believed, active.revision + 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -12445,12 +12496,12 @@ mod witness_tests {
|
|||
assert!(matches!(receipt, SubmitReceipt::Applied(_)), "{receipt:?}");
|
||||
assert_eq!(
|
||||
knows(&kernel, world.villager, world.asteroid),
|
||||
seen(Confidence::Certain),
|
||||
seen(Confidence::Certain, seeded.revision + 1),
|
||||
"the forgotten subject was not re-reached"
|
||||
);
|
||||
assert_eq!(
|
||||
knows(&kernel, world.neighbour, world.asteroid),
|
||||
seen(Confidence::Believed),
|
||||
seen(Confidence::Believed, active.revision + 1),
|
||||
"a holder the patch did not touch was overwritten"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -417,6 +417,12 @@ pub(crate) enum KnowledgeSource {
|
|||
pub(crate) struct Knowledge {
|
||||
pub(crate) confidence: Confidence,
|
||||
pub(crate) source: KnowledgeSource,
|
||||
/// The revision at which this subject acquired this row: witnessed,
|
||||
/// told, shown, or seeded by an author. Written by every writer of this
|
||||
/// map (PA.f44), so the Projector's recency marker can tell a row minted
|
||||
/// with no `minted_at` — a patch, a seed, a ruled fact — from one the
|
||||
/// subject already held before its last act.
|
||||
pub(crate) acquired_at: u64,
|
||||
}
|
||||
|
||||
/// The source an author may write. `Told` is unrepresentable here: only
|
||||
|
|
|
|||
Loading…
Reference in a new issue