1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! What reached a model call (constitution VIII): one manifest of every
//! part handed to it — its kind, where it came from, its digest and size —
//! journaled with the call and named in the evidence. Producers add
//! entries through the runtime's one builder; nothing hashes or labels
//! context itself.
use serde::{Deserialize, Serialize};
/// What a part is. A new kind is a new variant, never a free string.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum ContextKind {
/// The project's rules.
Constitution,
/// The unit's plan.
Plan,
/// The requirement's text (a bug's triad).
Story,
/// The task.
Task,
/// A person's rejection reason.
Rejection,
/// The verifier's digest of an earlier attempt.
RetryDigest,
/// A GitHub issue's title and body (untrusted).
Issue,
/// A requirement's acceptance scenario.
Scenario,
/// A person's note to an author call (*Ask Again*, a reading).
Note,
/// A person's review comment, with its anchor's quote.
Comment,
/// The test layout speccoder detected from the checkout.
TestLayout,
/// What a person added to an Ask question (untrusted).
Chip,
}
/// One part handed to a call.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ContextEntry {
/// What it is.
pub kind: ContextKind,
/// Where it came from: `person`, `worktree`, `speccoder://<producer>/<name>`,
/// `github:<owner>/<repo>#<n>`.
pub source: String,
/// A path, an id or a part pointer (`/bundle/plan`).
pub reference: String,
/// Hex SHA-256 of the exact bytes given.
pub sha256: String,
/// Their size.
pub bytes: u64,
/// Cut to a budget.
pub cut: bool,
}
/// Every part of one call, sorted by kind and reference.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ContextManifest {
/// The parts.
pub entries: Vec<ContextEntry>,
/// Hex SHA-256 of [`ContextManifest::canonical`].
pub digest: String,
}
impl ContextManifest {
/// The entries' canonical JSON (sorted): what the digest is over.
#[must_use]
pub fn canonical(entries: &[ContextEntry]) -> String {
let mut sorted = entries.to_vec();
sorted.sort_by(|a, b| (a.kind, &a.reference).cmp(&(b.kind, &b.reference)));
serde_json::to_string(&sorted).unwrap_or_default()
}
/// The entries, sorted, with the digest `hash` gives over them.
#[must_use]
pub fn of(mut entries: Vec<ContextEntry>, hash: impl Fn(&[u8]) -> String) -> Self {
let digest = hash(Self::canonical(&entries).as_bytes());
entries.sort_by(|a, b| (a.kind, &a.reference).cmp(&(b.kind, &b.reference)));
Self { entries, digest }
}
/// The entries of `kind`.
pub fn of_kind(&self, kind: ContextKind) -> impl Iterator<Item = &ContextEntry> {
self.entries.iter().filter(move |e| e.kind == kind)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(kind: ContextKind, reference: &str) -> ContextEntry {
ContextEntry {
kind,
source: "person".to_owned(),
reference: reference.to_owned(),
sha256: "00".to_owned(),
bytes: 2,
cut: false,
}
}
#[test]
fn the_digest_does_not_depend_on_the_order_parts_were_added() {
let h = |b: &[u8]| format!("{}", b.len());
let a = ContextManifest::of(
vec![
entry(ContextKind::Issue, "#42"),
entry(ContextKind::Story, "/triad"),
],
h,
);
let b = ContextManifest::of(
vec![
entry(ContextKind::Story, "/triad"),
entry(ContextKind::Issue, "#42"),
],
h,
);
assert_eq!(a, b);
assert_eq!(a.entries[0].kind, ContextKind::Story);
assert_eq!(a.of_kind(ContextKind::Issue).count(), 1);
}
}