hecks 3.12.0 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
- data/lib/hecks/adapters/driven/script_runner.adapter +3 -0
- data/lib/hecks/adapters/driven/script_runner.rb +93 -0
- data/lib/hecks/adapters/driven.rb +1 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +16 -12
- data/lib/hecks/bluebook/meta_validator.rb +28 -21
- data/lib/hecks/bluebook/named_pattern.rb +33 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- data/lib/hecks/deploy/bluebook/deploy.bluebook +35 -35
- data/lib/hecks/framework/bluebook/change.behaviors +30 -0
- data/lib/hecks/framework/bluebook/change.bluebook +162 -0
- data/lib/hecks/framework/bluebook/change.hecksagon +21 -0
- data/lib/hecks/framework/change.oidc.json +34 -0
- data/lib/hecks/framework/console_settings.oidc.json +103 -0
- data/lib/hecks/framework/governance.oidc.json +28 -0
- data/lib/hecks/framework/identity.oidc.json +18 -0
- data/lib/hecks/framework/privacy.oidc.json +30 -0
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- data/lib/hecks/gate/stages.yml +3 -0
- data/lib/hecks/grammar/translation.oidc.json +48 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +12 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -1
- data/lib/hecks/hecks/codebase.bluebook +37 -37
- data/lib/hecks/hecks/custodian.bluebook +14 -14
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/rehearsal.bluebook +3 -3
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- data/lib/hecks/projections/deploy/fargate/cdn/lines.rb +12 -1
- data/lib/hecks/projections/site/admin_cms/templates/membership.rb +10 -7
- data/lib/hecks/projections/site/edge/behaviours.rb +10 -6
- data/lib/hecks/projections/site/edge/checks.rb +2 -0
- data/lib/hecks/projections/site/edge/function_checks.rb +104 -0
- data/lib/hecks/projections/site/edge/function_code.rb +86 -0
- data/lib/hecks/projections/site/edge/redirect_host.js.tmpl +65 -0
- data/lib/hecks/projections/site/edge.rb +35 -9
- data/lib/hecks/projections/site/site_cdn.rb +21 -7
- data/lib/hecks/projections/site/table/row_builder.rb +12 -2
- data/lib/hecks/projections/site/table.rb +4 -2
- data/lib/hecks/quality_control/quality_control.bluebook +51 -51
- data/lib/hecks/site/bluebook/site.bluebook +21 -6
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- data/lib/hecks/tools/oidc_manifests.rb +44 -6
- data/lib/hecks/tools/site_routes/templates.rb +13 -9
- data/lib/hecks/version.rb +1 -1
- data/prebuilt/{d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal → 1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal} +0 -0
- data/prebuilt/{9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal → a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal} +0 -0
- data/prebuilt/verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json +1 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/auth.rs +58 -0
- data/rust/host/src/payments/roles.rs +159 -0
- data/rust/host/src/payments/tests.rs +25 -0
- data/rust/host/src/payments.rs +20 -11
- data/rust/host/src/platform_signin/session.rs +1 -1
- data/rust/parser/src/build/mod.rs +1 -0
- data/rust/parser/src/build/named_pattern.rs +23 -0
- data/rust/parser/src/keywords.rs +6 -0
- data/rust/parser/src/parse/mod.rs +22 -1
- metadata +24 -7
- data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +0 -1
- /data/lib/hecks/framework/{oidc.json → compliance.oidc.json} +0 -0
- /data/lib/hecks/grammar/{oidc.json → expression.oidc.json} +0 -0
data/rust/host/HECKS_RELEASE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.14.0
|
data/rust/host/src/auth.rs
CHANGED
|
@@ -260,6 +260,43 @@ fn identity_id_from_state(state: &Value) -> Option<String> {
|
|
|
260
260
|
})
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/// Resolves an external identity the way the Google callback does: the PostgresEra head first,
|
|
264
|
+
/// then the flat journal's `instances`. A domain whose Identity chapter keeps no head of its own
|
|
265
|
+
/// (its heads cover its own aggregates and the membership aggregate) holds the link only there.
|
|
266
|
+
///
|
|
267
|
+
/// # Errors
|
|
268
|
+
///
|
|
269
|
+
/// Returns the flat journal's read error, except that a missing flat journal means no link there. A
|
|
270
|
+
/// head that cannot be read is logged and skipped, as the Google callback does, so the flat journal
|
|
271
|
+
/// still gets its turn.
|
|
272
|
+
pub async fn resolve_identity_anywhere(
|
|
273
|
+
client: &Mutex<Client>,
|
|
274
|
+
wasm_path: &Path,
|
|
275
|
+
domain_ir: &Value,
|
|
276
|
+
issuer: &str,
|
|
277
|
+
subject: &str,
|
|
278
|
+
) -> anyhow::Result<Option<String>> {
|
|
279
|
+
match resolve_identity_from_head(client, domain_ir, issuer, subject).await {
|
|
280
|
+
Ok(Some(found)) => return Ok(Some(found)),
|
|
281
|
+
Ok(None) => {}
|
|
282
|
+
Err(error) => eprintln!("resolve_identity: identity head: {error:#}"),
|
|
283
|
+
}
|
|
284
|
+
let read = match dispatch::read(client, wasm_path).await {
|
|
285
|
+
Ok(read) => read,
|
|
286
|
+
Err(error) if flat_journal_is_absent(&error) => return Ok(None),
|
|
287
|
+
Err(error) => return Err(error),
|
|
288
|
+
};
|
|
289
|
+
Ok(resolve_identity(read.get("instances").unwrap_or(&Value::Null), issuer, subject))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// A store with heads only has no flat journal to read; that is "no link there", not an outage.
|
|
293
|
+
fn flat_journal_is_absent(error: &anyhow::Error) -> bool {
|
|
294
|
+
error
|
|
295
|
+
.chain()
|
|
296
|
+
.filter_map(|cause| cause.downcast_ref::<tokio_postgres::Error>())
|
|
297
|
+
.any(|db| db.code() == Some(&tokio_postgres::error::SqlState::UNDEFINED_TABLE))
|
|
298
|
+
}
|
|
299
|
+
|
|
263
300
|
/// Identity lives in the PostgresEra head, not `dispatch::read`'s
|
|
264
301
|
/// `instances` — tries both the domain-qualified and the bare relation name.
|
|
265
302
|
pub async fn resolve_identity_from_head(
|
|
@@ -1102,6 +1139,27 @@ mod tests {
|
|
|
1102
1139
|
assert!(resolve_identity(&instances, "google", "nope").is_none());
|
|
1103
1140
|
}
|
|
1104
1141
|
|
|
1142
|
+
#[test]
|
|
1143
|
+
fn resolve_identity_finds_a_link_that_only_the_flat_journal_holds() {
|
|
1144
|
+
// The shape `ExternalIdentifier.Link` leaves in the flat snapshot of a domain whose Identity
|
|
1145
|
+
// chapter has no head of its own; the key carries the issuer URL and the subject.
|
|
1146
|
+
let instances = json!({
|
|
1147
|
+
"Identity::ExternalIdentifier#https://accounts.google.com:111": {
|
|
1148
|
+
"identity": "471ef402-8609-4fa4-9288-6372fc74650d",
|
|
1149
|
+
"key": {"value": "https://accounts.google.com:111"},
|
|
1150
|
+
"issuer": {"value": "https://accounts.google.com"},
|
|
1151
|
+
"subject": {"value": "111"},
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
let read = json!({ "instances": instances });
|
|
1155
|
+
assert_eq!(
|
|
1156
|
+
resolve_identity(read.get("instances").unwrap_or(&Value::Null), "https://accounts.google.com", "111").as_deref(),
|
|
1157
|
+
Some("471ef402-8609-4fa4-9288-6372fc74650d")
|
|
1158
|
+
);
|
|
1159
|
+
assert!(resolve_identity(read.get("instances").unwrap_or(&Value::Null), "https://accounts.google.com", "112").is_none());
|
|
1160
|
+
assert!(resolve_identity(&Value::Null, "https://accounts.google.com", "111").is_none());
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1105
1163
|
#[test]
|
|
1106
1164
|
fn resolve_identity_reads_the_2_0_identity_field() {
|
|
1107
1165
|
let instances = json!({
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
//! Which signed-in roles may manage a site's payments: a per-site setting, read once at boot.
|
|
2
|
+
//!
|
|
3
|
+
//! `HECKS_PAYMENTS_MANAGER_ROLES` is a comma-separated list of exact role names. Unset means
|
|
4
|
+
//! `Owner`, which is what the host did before the setting existed. A set value must be a
|
|
5
|
+
//! plausible list or the boot is refused; a refusal names the variable and the kind of mistake,
|
|
6
|
+
//! never the value. A caller manages payments when their role is in the list, compared exactly:
|
|
7
|
+
//! no case folding, no hierarchy, no wildcard.
|
|
8
|
+
|
|
9
|
+
use std::sync::OnceLock;
|
|
10
|
+
|
|
11
|
+
/// The variable that names the roles allowed to manage payments.
|
|
12
|
+
pub const VARIABLE: &str = "HECKS_PAYMENTS_MANAGER_ROLES";
|
|
13
|
+
/// The role that manages payments when the variable is unset.
|
|
14
|
+
pub const DEFAULT_ROLE: &str = "Owner";
|
|
15
|
+
/// The most roles one list may name.
|
|
16
|
+
pub const MAX_ENTRIES: usize = 8;
|
|
17
|
+
/// The longest role name, after trimming.
|
|
18
|
+
pub const MAX_ENTRY_LENGTH: usize = 32;
|
|
19
|
+
|
|
20
|
+
/// The roles that manage payments when nothing is configured.
|
|
21
|
+
pub fn default_roles() -> Vec<String> {
|
|
22
|
+
vec![DEFAULT_ROLE.to_string()]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Parses the variable's value; `None` (unset) gives the default list.
|
|
26
|
+
pub fn parse(raw: Option<&str>) -> Result<Vec<String>, String> {
|
|
27
|
+
let Some(raw) = raw else { return Ok(default_roles()) };
|
|
28
|
+
let refuse = |why: &str| Err(format!("{VARIABLE} is invalid: {why}"));
|
|
29
|
+
let entries: Vec<&str> = raw.split(',').map(str::trim).collect();
|
|
30
|
+
if entries.len() > MAX_ENTRIES {
|
|
31
|
+
return refuse(&format!("it names more than {MAX_ENTRIES} roles"));
|
|
32
|
+
}
|
|
33
|
+
for (index, entry) in entries.iter().enumerate() {
|
|
34
|
+
let position = index + 1;
|
|
35
|
+
if entry.is_empty() {
|
|
36
|
+
return refuse(&format!("entry {position} is empty; name at least one role and no blank entries"));
|
|
37
|
+
}
|
|
38
|
+
if entry.len() > MAX_ENTRY_LENGTH {
|
|
39
|
+
return refuse(&format!("entry {position} is longer than {MAX_ENTRY_LENGTH} characters"));
|
|
40
|
+
}
|
|
41
|
+
let starts_well = entry.starts_with(|c: char| c.is_ascii_alphanumeric());
|
|
42
|
+
if !starts_well || !entry.chars().all(|c| c.is_ascii_alphanumeric() || c == ' ' || c == '-') {
|
|
43
|
+
return refuse(&format!("entry {position} is not a role name: letters, digits, spaces and hyphens only, starting with a letter or digit"));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
Ok(entries.into_iter().map(String::from).collect())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Reads the setting into `cell` the first time and never again, whatever the environment
|
|
50
|
+
/// does afterwards. `read` stands for the environment lookup.
|
|
51
|
+
pub fn read_once(cell: &OnceLock<Result<Vec<String>, String>>, read: impl FnOnce() -> Option<String>) -> &Result<Vec<String>, String> {
|
|
52
|
+
cell.get_or_init(|| parse(read().as_deref()))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// This process's setting, read from the environment on first use (the boot check is the first).
|
|
56
|
+
pub fn configured() -> &'static Result<Vec<String>, String> {
|
|
57
|
+
static CELL: OnceLock<Result<Vec<String>, String>> = OnceLock::new();
|
|
58
|
+
read_once(&CELL, || std::env::var(VARIABLE).ok())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Refuses the boot when this process's setting is invalid.
|
|
62
|
+
pub fn check_boot() -> Result<(), String> {
|
|
63
|
+
configured().as_ref().map(|_| ()).map_err(Clone::clone)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// The roles for a request. An invalid setting never gets here (the boot was refused); should it
|
|
67
|
+
/// ever, only the default role manages, so a mistake narrows access instead of widening it.
|
|
68
|
+
pub fn manager_roles() -> Vec<String> {
|
|
69
|
+
configured().clone().unwrap_or_else(|_| default_roles())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Whether `role` is one of `roles`, by exact string match.
|
|
73
|
+
pub fn manages(roles: &[String], role: &str) -> bool {
|
|
74
|
+
roles.iter().any(|allowed| allowed == role)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[cfg(test)]
|
|
78
|
+
mod tests {
|
|
79
|
+
use super::*;
|
|
80
|
+
|
|
81
|
+
fn roles(raw: Option<&str>) -> Vec<String> {
|
|
82
|
+
parse(raw).unwrap()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[test]
|
|
86
|
+
fn unset_means_owner_only() {
|
|
87
|
+
let list = roles(None);
|
|
88
|
+
assert_eq!(list, ["Owner"]);
|
|
89
|
+
assert!(manages(&list, "Owner"));
|
|
90
|
+
assert!(!manages(&list, "Admin"));
|
|
91
|
+
assert!(!manages(&list, "Member"));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[test]
|
|
95
|
+
fn a_configured_list_admits_exactly_its_roles() {
|
|
96
|
+
let list = roles(Some("Owner,Admin"));
|
|
97
|
+
assert!(manages(&list, "Owner") && manages(&list, "Admin"));
|
|
98
|
+
assert!(!manages(&list, "Editor"));
|
|
99
|
+
assert!(!manages(&list, ""));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn whitespace_is_trimmed_and_case_is_exact() {
|
|
104
|
+
let list = roles(Some(" Owner , Account Admin "));
|
|
105
|
+
assert_eq!(list, ["Owner", "Account Admin"]);
|
|
106
|
+
let lower = roles(Some("admin"));
|
|
107
|
+
assert!(manages(&lower, "admin"));
|
|
108
|
+
assert!(!manages(&lower, "Admin"), "no case folding");
|
|
109
|
+
assert!(!manages(&roles(Some("Admin")), "admin"));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[test]
|
|
113
|
+
fn no_wildcard_and_no_hierarchy() {
|
|
114
|
+
assert!(parse(Some("*")).is_err());
|
|
115
|
+
assert!(!manages(&roles(Some("Admin")), "Owner"), "naming Admin does not admit Owner");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#[test]
|
|
119
|
+
fn an_invalid_setting_is_refused_naming_the_variable() {
|
|
120
|
+
let too_many = ["Role"; MAX_ENTRIES + 1].join(",");
|
|
121
|
+
let too_long = "A".repeat(MAX_ENTRY_LENGTH + 1);
|
|
122
|
+
for bad in ["", " ", ",", "Owner,", ",Owner", "Owner,,Admin", "Own/er", "Owner\nAdmin", "Own\u{7}er", "Admin;Owner", "-Admin", "Ad_min", "Régisseur", too_many.as_str(), too_long.as_str()] {
|
|
123
|
+
let message = parse(Some(bad)).expect_err(bad);
|
|
124
|
+
assert!(message.starts_with(VARIABLE), "{message}");
|
|
125
|
+
}
|
|
126
|
+
assert!(parse(Some(&"A".repeat(MAX_ENTRY_LENGTH))).is_ok());
|
|
127
|
+
assert!(parse(Some(&["Role"; MAX_ENTRIES].join(","))).is_ok());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[test]
|
|
131
|
+
fn a_refusal_never_echoes_the_value() {
|
|
132
|
+
let message = parse(Some("sk_live_SECRET/value")).unwrap_err();
|
|
133
|
+
assert!(!message.contains("sk_live"), "{message}");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[test]
|
|
137
|
+
fn the_setting_is_read_once() {
|
|
138
|
+
let cell = OnceLock::new();
|
|
139
|
+
let reads = std::cell::Cell::new(0);
|
|
140
|
+
let first = read_once(&cell, || {
|
|
141
|
+
reads.set(reads.get() + 1);
|
|
142
|
+
Some("Owner,Admin".to_string())
|
|
143
|
+
});
|
|
144
|
+
assert_eq!(first.as_ref().unwrap(), &["Owner", "Admin"]);
|
|
145
|
+
let second = read_once(&cell, || {
|
|
146
|
+
reads.set(reads.get() + 1);
|
|
147
|
+
Some("Editor".to_string())
|
|
148
|
+
});
|
|
149
|
+
assert_eq!(second.as_ref().unwrap(), &["Owner", "Admin"]);
|
|
150
|
+
assert_eq!(reads.get(), 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn an_invalid_value_stays_refused_once_read() {
|
|
155
|
+
let cell = OnceLock::new();
|
|
156
|
+
assert!(read_once(&cell, || Some("a/b".to_string())).is_err());
|
|
157
|
+
assert!(read_once(&cell, || None).is_err());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -197,6 +197,7 @@ async fn tenant(name: &str) -> Tenant {
|
|
|
197
197
|
direct_live_publishable_key: String::new(),
|
|
198
198
|
webhook_secret: Some(WEBHOOK_SECRET.to_string()),
|
|
199
199
|
operators: vec![OPERATOR.to_string()],
|
|
200
|
+
manager_roles: roles::default_roles(),
|
|
200
201
|
};
|
|
201
202
|
Tenant {
|
|
202
203
|
client,
|
|
@@ -412,6 +413,30 @@ async fn only_an_owner_may_connect_or_disconnect_and_only_the_operator_may_enabl
|
|
|
412
413
|
assert_eq!(t.status().await, "enabled");
|
|
413
414
|
}
|
|
414
415
|
|
|
416
|
+
#[tokio::test]
|
|
417
|
+
async fn a_site_that_lists_admin_as_a_payments_manager_lets_an_admin_manage() {
|
|
418
|
+
let mut t = tenant("hecks_pay_test_manager_roles").await;
|
|
419
|
+
t.platform.manager_roles = vec!["Owner".to_string(), "Admin".to_string()];
|
|
420
|
+
|
|
421
|
+
let (status, body) = t.call("GET", "/payments/connection", json!({}), Some(ADMIN)).await;
|
|
422
|
+
assert_eq!((status, body["can_manage"].clone()), (200, json!(true)), "{body}");
|
|
423
|
+
t.connect().await;
|
|
424
|
+
assert_eq!(t.status().await, "connected");
|
|
425
|
+
let (status, _) = t.call("POST", "/payments/connection/disconnect", json!({}), Some(ADMIN)).await;
|
|
426
|
+
assert_eq!(status, 200);
|
|
427
|
+
assert_eq!(t.status().await, "disconnected");
|
|
428
|
+
|
|
429
|
+
// Enabling stays with the operator, and the Owner still manages.
|
|
430
|
+
let (status, body) = t.call("GET", "/payments/connection", json!({}), Some(OWNER)).await;
|
|
431
|
+
assert_eq!((status, body["can_manage"].clone()), (200, json!(true)), "{body}");
|
|
432
|
+
// Someone signed out, or not listed, is refused with today's message.
|
|
433
|
+
let (status, _) = t.call("GET", "/payments/connection", json!({}), None).await;
|
|
434
|
+
assert_eq!(status, 401);
|
|
435
|
+
t.platform.manager_roles = vec!["Editor".to_string()];
|
|
436
|
+
let (status, body) = t.call("POST", "/payments/connection/direct", json!({"mode": "test"}), Some(OWNER)).await;
|
|
437
|
+
assert_eq!((status, body["error"].as_str()), (403, Some("only an Owner can manage payments")));
|
|
438
|
+
}
|
|
439
|
+
|
|
415
440
|
#[tokio::test]
|
|
416
441
|
async fn an_admin_granted_owner_passes_the_payments_gate_and_keeps_the_admin_gate() {
|
|
417
442
|
let t = tenant("hecks_pay_test_owner_bootstrap").await;
|
data/rust/host/src/payments.rs
CHANGED
|
@@ -15,6 +15,8 @@ use tokio::sync::Mutex;
|
|
|
15
15
|
use tokio_postgres::Client;
|
|
16
16
|
|
|
17
17
|
mod keystore;
|
|
18
|
+
/// Which member roles may manage payments (`HECKS_PAYMENTS_MANAGER_ROLES`).
|
|
19
|
+
pub mod roles;
|
|
18
20
|
use keystore::{KeyStore, StoredDocument, StoredKeys};
|
|
19
21
|
|
|
20
22
|
const CONNECTION_SLUG: &str = "payments";
|
|
@@ -63,11 +65,16 @@ pub struct PlatformConfig {
|
|
|
63
65
|
pub webhook_secret: Option<String>,
|
|
64
66
|
/// Lower-cased emails allowed to enable or disable real payments.
|
|
65
67
|
pub operators: Vec<String>,
|
|
68
|
+
/// The exact role names that may manage payments (`HECKS_PAYMENTS_MANAGER_ROLES`,
|
|
69
|
+
/// default `Owner`), read once at boot; see `roles`.
|
|
70
|
+
pub manager_roles: Vec<String>,
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
/// Refuses the boot
|
|
69
|
-
/// names no secret; off AWS, or with
|
|
73
|
+
/// Refuses the boot when `HECKS_PAYMENTS_MANAGER_ROLES` is invalid, and on AWS when
|
|
74
|
+
/// checkout is enabled but `PAYMENTS_ACCOUNT_SECRET_ID` names no secret; off AWS, or with
|
|
75
|
+
/// checkout off, the host boots as usual.
|
|
70
76
|
pub fn check_boot(checkout_enabled: bool) -> Result<(), String> {
|
|
77
|
+
roles::check_boot()?;
|
|
71
78
|
keystore::check_boot(checkout_enabled)
|
|
72
79
|
}
|
|
73
80
|
|
|
@@ -102,6 +109,7 @@ impl PlatformConfig {
|
|
|
102
109
|
.map(|email| email.trim().to_lowercase())
|
|
103
110
|
.filter(|email| !email.is_empty())
|
|
104
111
|
.collect(),
|
|
112
|
+
manager_roles: roles::manager_roles(),
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
|
|
@@ -276,7 +284,7 @@ fn refusal_message(refusal: &Value) -> String {
|
|
|
276
284
|
}
|
|
277
285
|
|
|
278
286
|
struct Caller {
|
|
279
|
-
|
|
287
|
+
manager: bool,
|
|
280
288
|
operator: bool,
|
|
281
289
|
}
|
|
282
290
|
|
|
@@ -288,14 +296,14 @@ async fn caller(domain_ir: &Value, cookies: &HashMap<String, String>, secret: &s
|
|
|
288
296
|
return Err(not_logged_in());
|
|
289
297
|
};
|
|
290
298
|
match auth::active_role(client, domain_ir, &email).await {
|
|
291
|
-
Ok(Some(role)) => Ok(Caller {
|
|
299
|
+
Ok(Some(role)) => Ok(Caller { manager: roles::manages(&platform.manager_roles, &role), operator: platform.is_operator(&email) }),
|
|
292
300
|
Ok(None) => Err(not_logged_in()),
|
|
293
301
|
Err(e) => Err(json_error(500, &format!("members lookup failed: {e}"))),
|
|
294
302
|
}
|
|
295
303
|
}
|
|
296
304
|
|
|
297
|
-
fn
|
|
298
|
-
if caller.
|
|
305
|
+
fn require_manager(caller: &Caller) -> Result<(), Value> {
|
|
306
|
+
if caller.manager { Ok(()) } else { Err(json_error(403, "only an Owner can manage payments")) }
|
|
299
307
|
}
|
|
300
308
|
|
|
301
309
|
fn require_operator(caller: &Caller) -> Result<(), Value> {
|
|
@@ -322,7 +330,7 @@ fn connection_json(connection: Option<&Connection>, platform: &PlatformConfig, c
|
|
|
322
330
|
"direct_modes": platform.direct_modes(),
|
|
323
331
|
"direct": connection.is_some_and(Connection::is_direct),
|
|
324
332
|
"can_save_keys": platform.can_save_keys(),
|
|
325
|
-
"can_manage": caller.
|
|
333
|
+
"can_manage": caller.manager,
|
|
326
334
|
"can_enable": caller.operator,
|
|
327
335
|
})
|
|
328
336
|
}
|
|
@@ -375,9 +383,9 @@ pub async fn route(
|
|
|
375
383
|
})
|
|
376
384
|
}
|
|
377
385
|
|
|
378
|
-
// GET: readable by
|
|
386
|
+
// GET: readable by a payments manager or the operator, nobody else.
|
|
379
387
|
async fn show(caller: &Caller, platform: &PlatformConfig, client: &Mutex<Client>, wasm_path: &Path, config: &LineageConfig) -> Value {
|
|
380
|
-
if !caller.
|
|
388
|
+
if !caller.manager && !caller.operator {
|
|
381
389
|
return json_error(403, "not permitted");
|
|
382
390
|
}
|
|
383
391
|
connection_response(caller, platform, client, wasm_path, config).await
|
|
@@ -429,7 +437,7 @@ async fn record_connection(
|
|
|
429
437
|
// name), and the key itself is never stored or logged.
|
|
430
438
|
#[allow(clippy::too_many_arguments)]
|
|
431
439
|
async fn direct_route(caller: &Caller, raw_body: &str, platform: &PlatformConfig, client: &Mutex<Client>, wasm_path: &Path, config: &LineageConfig, invoker: &dyn LambdaInvoker) -> Value {
|
|
432
|
-
if let Err(response) =
|
|
440
|
+
if let Err(response) = require_manager(caller) {
|
|
433
441
|
return response;
|
|
434
442
|
}
|
|
435
443
|
let body = match parse_body(raw_body) {
|
|
@@ -462,7 +470,7 @@ async fn direct_route(caller: &Caller, raw_body: &str, platform: &PlatformConfig
|
|
|
462
470
|
// guests have paid through this account. Disconnecting the business's own account
|
|
463
471
|
// also removes its webhook and deletes the saved keys.
|
|
464
472
|
async fn disconnect_route(caller: &Caller, platform: &PlatformConfig, client: &Mutex<Client>, wasm_path: &Path, config: &LineageConfig, invoker: &dyn LambdaInvoker) -> Value {
|
|
465
|
-
if let Err(response) =
|
|
473
|
+
if let Err(response) = require_manager(caller) {
|
|
466
474
|
return response;
|
|
467
475
|
}
|
|
468
476
|
let existing = match current_connection(client, wasm_path, config).await {
|
|
@@ -762,6 +770,7 @@ pub(crate) fn test_platform() -> PlatformConfig {
|
|
|
762
770
|
direct_live_publishable_key: String::new(),
|
|
763
771
|
webhook_secret: None,
|
|
764
772
|
operators: Vec::new(),
|
|
773
|
+
manager_roles: roles::default_roles(),
|
|
765
774
|
}
|
|
766
775
|
}
|
|
767
776
|
|
|
@@ -123,7 +123,7 @@ pub struct HostMembers<'a> {
|
|
|
123
123
|
#[async_trait]
|
|
124
124
|
impl MemberDirectory for HostMembers<'_> {
|
|
125
125
|
async fn identity_for(&self, issuer: &str, subject: &str) -> anyhow::Result<Option<String>> {
|
|
126
|
-
auth::
|
|
126
|
+
auth::resolve_identity_anywhere(self.client, self.wasm_path, self.domain_ir, issuer, subject).await
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
async fn session_for_identity(&self, identity_id: &str) -> anyhow::Result<Option<Session>> {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//! The patterns a bluebook names instead of spelling: `matches: :snake_name` stands for exactly
|
|
2
|
+
//! its regex. Mirrors `Hecks::Bluebook::NamedPattern`; spec/named_pattern_spec.rb holds the two
|
|
3
|
+
//! tables to each other.
|
|
4
|
+
|
|
5
|
+
/// Name => the regex source it stands for.
|
|
6
|
+
const TABLE: &[(&str, &str)] = &[
|
|
7
|
+
("present", r"[^ \t\n\r]"),
|
|
8
|
+
("not_empty", r"[^\t\n\r]"),
|
|
9
|
+
("no_whitespace", r"^[^ \t\n\r]+$"),
|
|
10
|
+
("snake_name", r"^[a-z][a-z0-9_]*$"),
|
|
11
|
+
("git_sha", r"^[0-9a-fA-F]{7,40}$"),
|
|
12
|
+
("digits", r"^[0-9]+$"),
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/// The regex source `name` stands for, or `None` when the language gives no such name.
|
|
16
|
+
pub fn source(name: &str) -> Option<&'static str> {
|
|
17
|
+
TABLE.iter().find(|(given, _)| *given == name).map(|(_, source)| *source)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Every name the language gives, in table order.
|
|
21
|
+
pub fn names() -> Vec<&'static str> {
|
|
22
|
+
TABLE.iter().map(|(name, _)| *name).collect()
|
|
23
|
+
}
|
data/rust/parser/src/keywords.rs
CHANGED
|
@@ -256,6 +256,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
256
256
|
ArgumentRow { keyword: "attribute", context: "Aggregate", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
257
257
|
ArgumentRow { keyword: "attribute", context: "Aggregate", at: "", named: "optional", kind: "flag", required: "false", fills: "optional", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
258
258
|
ArgumentRow { keyword: "attribute", context: "Aggregate", at: "", named: "pattern", kind: "text", required: "false", fills: "pattern", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
259
|
+
ArgumentRow { keyword: "attribute", context: "Aggregate", at: "", named: "matches", kind: "symbol", required: "false", fills: "matches", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
259
260
|
ArgumentRow { keyword: "attribute", context: "Aggregate", at: "", named: "admits", kind: "text", required: "false", fills: "admits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
260
261
|
ArgumentRow { keyword: "attribute", context: "Aggregate", at: "", named: "one_of", kind: "list", required: "false", fills: "one_of", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
261
262
|
ArgumentRow { keyword: "invariant", context: "Aggregate", at: "1", named: "", kind: "text", required: "true", fills: "description", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -356,6 +357,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
356
357
|
ArgumentRow { keyword: "attribute", context: "Command", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
357
358
|
ArgumentRow { keyword: "attribute", context: "Command", at: "", named: "optional", kind: "flag", required: "false", fills: "optional", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
358
359
|
ArgumentRow { keyword: "attribute", context: "Command", at: "", named: "pattern", kind: "text", required: "false", fills: "pattern", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
360
|
+
ArgumentRow { keyword: "attribute", context: "Command", at: "", named: "matches", kind: "symbol", required: "false", fills: "matches", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
359
361
|
ArgumentRow { keyword: "attribute", context: "Command", at: "", named: "admits", kind: "text", required: "false", fills: "admits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
360
362
|
ArgumentRow { keyword: "attribute", context: "Command", at: "", named: "one_of", kind: "list", required: "false", fills: "one_of", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
361
363
|
ArgumentRow { keyword: "corrects", context: "Command", at: "1", named: "", kind: "text", required: "true", fills: "target", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -380,6 +382,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
380
382
|
ArgumentRow { keyword: "attribute", context: "Entity", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
381
383
|
ArgumentRow { keyword: "attribute", context: "Entity", at: "", named: "optional", kind: "flag", required: "false", fills: "optional", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
382
384
|
ArgumentRow { keyword: "attribute", context: "Entity", at: "", named: "pattern", kind: "text", required: "false", fills: "pattern", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
385
|
+
ArgumentRow { keyword: "attribute", context: "Entity", at: "", named: "matches", kind: "symbol", required: "false", fills: "matches", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
383
386
|
ArgumentRow { keyword: "attribute", context: "Entity", at: "", named: "admits", kind: "text", required: "false", fills: "admits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
384
387
|
ArgumentRow { keyword: "attribute", context: "Entity", at: "", named: "one_of", kind: "list", required: "false", fills: "one_of", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
385
388
|
ArgumentRow { keyword: "reference_to", context: "Entity", at: "1", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -446,6 +449,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
446
449
|
ArgumentRow { keyword: "attribute", context: "Query", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
447
450
|
ArgumentRow { keyword: "attribute", context: "Query", at: "", named: "optional", kind: "flag", required: "false", fills: "optional", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
448
451
|
ArgumentRow { keyword: "attribute", context: "Query", at: "", named: "pattern", kind: "text", required: "false", fills: "pattern", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
452
|
+
ArgumentRow { keyword: "attribute", context: "Query", at: "", named: "matches", kind: "symbol", required: "false", fills: "matches", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
449
453
|
ArgumentRow { keyword: "attribute", context: "Query", at: "", named: "admits", kind: "text", required: "false", fills: "admits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
450
454
|
ArgumentRow { keyword: "attribute", context: "Query", at: "", named: "one_of", kind: "list", required: "false", fills: "one_of", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
451
455
|
ArgumentRow { keyword: "reference_to", context: "Query", at: "1", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -464,6 +468,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
464
468
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
465
469
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "optional", kind: "flag", required: "false", fills: "optional", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
466
470
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "pattern", kind: "text", required: "false", fills: "pattern", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
471
|
+
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "matches", kind: "symbol", required: "false", fills: "matches", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
467
472
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "admits", kind: "text", required: "false", fills: "admits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
468
473
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "one_of", kind: "list", required: "false", fills: "one_of", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
469
474
|
ArgumentRow { keyword: "invariant", context: "ValueObject", at: "1", named: "", kind: "text", required: "true", fills: "description", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -500,6 +505,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
500
505
|
ArgumentRow { keyword: "attribute", context: "PortOperation", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
501
506
|
ArgumentRow { keyword: "attribute", context: "PortOperation", at: "", named: "optional", kind: "flag", required: "false", fills: "optional", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
502
507
|
ArgumentRow { keyword: "attribute", context: "PortOperation", at: "", named: "pattern", kind: "text", required: "false", fills: "pattern", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
508
|
+
ArgumentRow { keyword: "attribute", context: "PortOperation", at: "", named: "matches", kind: "symbol", required: "false", fills: "matches", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
503
509
|
ArgumentRow { keyword: "attribute", context: "PortOperation", at: "", named: "admits", kind: "text", required: "false", fills: "admits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
504
510
|
ArgumentRow { keyword: "attribute", context: "PortOperation", at: "", named: "one_of", kind: "list", required: "false", fills: "one_of", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
505
511
|
ArgumentRow { keyword: "emits", context: "PortOperation", at: "1", named: "", kind: "text", required: "true", fills: "emits", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -1134,7 +1134,28 @@ pub(crate) fn build_attribute(
|
|
|
1134
1134
|
let (type_name, list, closed_set) = resolve_type_expression(file, line, word, &name, text)?;
|
|
1135
1135
|
let default = named_raw(args, "default").map(ruby_value::read);
|
|
1136
1136
|
let optional = named_flag(args, "optional");
|
|
1137
|
-
|
|
1137
|
+
// `matches: :name` is the pattern the language gives that name.
|
|
1138
|
+
let pattern = match named_symbol(args, "matches") {
|
|
1139
|
+
Some(given) => {
|
|
1140
|
+
if named_text(args, "pattern").is_some() {
|
|
1141
|
+
return Err(Diagnostic::new(file, line, "matches: and pattern: both state the pattern".to_string()));
|
|
1142
|
+
}
|
|
1143
|
+
match crate::build::named_pattern::source(&given) {
|
|
1144
|
+
Some(source) => Some(source.to_string()),
|
|
1145
|
+
None => {
|
|
1146
|
+
return Err(Diagnostic::new(
|
|
1147
|
+
file,
|
|
1148
|
+
line,
|
|
1149
|
+
format!(
|
|
1150
|
+
"matches: {given:?} is not a named pattern — the names are {}",
|
|
1151
|
+
crate::build::named_pattern::names().join(", ")
|
|
1152
|
+
),
|
|
1153
|
+
))
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
None => named_text(args, "pattern"),
|
|
1158
|
+
};
|
|
1138
1159
|
let admits = named_text(args, "admits");
|
|
1139
1160
|
// Fail closed at declaration time on a pattern outside the shared subset, as Ruby does.
|
|
1140
1161
|
if let Some(pat) = &pattern {
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: prism
|
|
@@ -97,6 +97,8 @@ files:
|
|
|
97
97
|
- lib/hecks/adapters/driven/postgres_era.adapter
|
|
98
98
|
- lib/hecks/adapters/driven/prism.adapter
|
|
99
99
|
- lib/hecks/adapters/driven/prism.rb
|
|
100
|
+
- lib/hecks/adapters/driven/script_runner.adapter
|
|
101
|
+
- lib/hecks/adapters/driven/script_runner.rb
|
|
100
102
|
- lib/hecks/adapters/driven/secure_random_identity.adapter
|
|
101
103
|
- lib/hecks/adapters/driven/secure_random_identity.rb
|
|
102
104
|
- lib/hecks/adapters/driven/sql_query_builder.rb
|
|
@@ -320,6 +322,7 @@ files:
|
|
|
320
322
|
- lib/hecks/bluebook/model_check/reference_checks.rb
|
|
321
323
|
- lib/hecks/bluebook/model_check/saga_checks.rb
|
|
322
324
|
- lib/hecks/bluebook/model_check/verbs.rb
|
|
325
|
+
- lib/hecks/bluebook/named_pattern.rb
|
|
323
326
|
- lib/hecks/bluebook/pattern_subset.rb
|
|
324
327
|
- lib/hecks/bluebook/pattern_subset/refusals.rb
|
|
325
328
|
- lib/hecks/bluebook/pattern_subset/scan.rb
|
|
@@ -454,13 +457,21 @@ files:
|
|
|
454
457
|
- lib/hecks/forms/value_object_shape.rb
|
|
455
458
|
- lib/hecks/fqn.rb
|
|
456
459
|
- lib/hecks/framework.rb
|
|
460
|
+
- lib/hecks/framework/bluebook/change.behaviors
|
|
461
|
+
- lib/hecks/framework/bluebook/change.bluebook
|
|
462
|
+
- lib/hecks/framework/bluebook/change.hecksagon
|
|
457
463
|
- lib/hecks/framework/bluebook/compliance.bluebook
|
|
458
464
|
- lib/hecks/framework/bluebook/console_settings.bluebook
|
|
459
465
|
- lib/hecks/framework/bluebook/framework.hecksagon
|
|
460
466
|
- lib/hecks/framework/bluebook/governance.bluebook
|
|
461
467
|
- lib/hecks/framework/bluebook/identity.bluebook
|
|
462
468
|
- lib/hecks/framework/bluebook/privacy.bluebook
|
|
463
|
-
- lib/hecks/framework/oidc.json
|
|
469
|
+
- lib/hecks/framework/change.oidc.json
|
|
470
|
+
- lib/hecks/framework/compliance.oidc.json
|
|
471
|
+
- lib/hecks/framework/console_settings.oidc.json
|
|
472
|
+
- lib/hecks/framework/governance.oidc.json
|
|
473
|
+
- lib/hecks/framework/identity.oidc.json
|
|
474
|
+
- lib/hecks/framework/privacy.oidc.json
|
|
464
475
|
- lib/hecks/freezer.rb
|
|
465
476
|
- lib/hecks/fuzzing.rb
|
|
466
477
|
- lib/hecks/fuzzing/bounded_exhaustive_expressions.rb
|
|
@@ -574,11 +585,12 @@ files:
|
|
|
574
585
|
- lib/hecks/grammar/evolve/keywords.rb
|
|
575
586
|
- lib/hecks/grammar/evolve/seeds.rb
|
|
576
587
|
- lib/hecks/grammar/expression.bluebook
|
|
588
|
+
- lib/hecks/grammar/expression.oidc.json
|
|
577
589
|
- lib/hecks/grammar/expression_operators.json
|
|
578
590
|
- lib/hecks/grammar/grammar.hecksagon
|
|
579
|
-
- lib/hecks/grammar/oidc.json
|
|
580
591
|
- lib/hecks/grammar/operators.rb
|
|
581
592
|
- lib/hecks/grammar/translation.bluebook
|
|
593
|
+
- lib/hecks/grammar/translation.oidc.json
|
|
582
594
|
- lib/hecks/hecks/adapters/bluebook_report.rb
|
|
583
595
|
- lib/hecks/hecks/adapters/codebase/codemods.rb
|
|
584
596
|
- lib/hecks/hecks/adapters/codebase/conformance.rb
|
|
@@ -1177,7 +1189,10 @@ files:
|
|
|
1177
1189
|
- lib/hecks/projections/site/edge/behaviours.rb
|
|
1178
1190
|
- lib/hecks/projections/site/edge/checks.rb
|
|
1179
1191
|
- lib/hecks/projections/site/edge/findings.rb
|
|
1192
|
+
- lib/hecks/projections/site/edge/function_checks.rb
|
|
1193
|
+
- lib/hecks/projections/site/edge/function_code.rb
|
|
1180
1194
|
- lib/hecks/projections/site/edge/pattern.rb
|
|
1195
|
+
- lib/hecks/projections/site/edge/redirect_host.js.tmpl
|
|
1181
1196
|
- lib/hecks/projections/site/edge/route_checks.rb
|
|
1182
1197
|
- lib/hecks/projections/site/edge/row_reader.rb
|
|
1183
1198
|
- lib/hecks/projections/site/edge/rules.rb
|
|
@@ -1622,9 +1637,9 @@ files:
|
|
|
1622
1637
|
- lib/rubocop/cop/hecks/symbol_string_key_mix.rb
|
|
1623
1638
|
- lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb
|
|
1624
1639
|
- lib/rubocop/cop/hecks/untyped_raise.rb
|
|
1625
|
-
- prebuilt/
|
|
1626
|
-
- prebuilt/
|
|
1627
|
-
- prebuilt/verdicts-
|
|
1640
|
+
- prebuilt/1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal
|
|
1641
|
+
- prebuilt/a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal
|
|
1642
|
+
- prebuilt/verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json
|
|
1628
1643
|
- qa/settings.yml
|
|
1629
1644
|
- rust/.cargo/config.toml
|
|
1630
1645
|
- rust/.cargo/mutants.toml
|
|
@@ -1720,6 +1735,7 @@ files:
|
|
|
1720
1735
|
- rust/host/src/needs.rs
|
|
1721
1736
|
- rust/host/src/payments.rs
|
|
1722
1737
|
- rust/host/src/payments/keystore.rs
|
|
1738
|
+
- rust/host/src/payments/roles.rs
|
|
1723
1739
|
- rust/host/src/payments/tests.rs
|
|
1724
1740
|
- rust/host/src/platform_signin/attack_tests.rs
|
|
1725
1741
|
- rust/host/src/platform_signin/config.rs
|
|
@@ -1790,6 +1806,7 @@ files:
|
|
|
1790
1806
|
- rust/parser/src/build/closed_sets.rs
|
|
1791
1807
|
- rust/parser/src/build/identity.rs
|
|
1792
1808
|
- rust/parser/src/build/mod.rs
|
|
1809
|
+
- rust/parser/src/build/named_pattern.rs
|
|
1793
1810
|
- rust/parser/src/build/naming.rs
|
|
1794
1811
|
- rust/parser/src/build/pattern_subset.rs
|
|
1795
1812
|
- rust/parser/src/build/query_derive.rs
|