hecks 3.2.1 → 3.4.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/lib/hecks/bluebook/assembly/contracts.rb +2 -1
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +21 -23
- data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
- data/lib/hecks/bluebook/query.rb +8 -5
- data/lib/hecks/cli/interview_draft.rb +88 -33
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +198 -0
- data/lib/hecks/corpus.rb +3 -5
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door_scope.rb +2 -1
- data/lib/hecks/doors.rb +0 -17
- data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +1 -4
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
- data/lib/hecks/hecks/adapters/git.rb +59 -0
- data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
- data/lib/hecks/hecks/adapters/site_toolchain.rb +67 -0
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/custodian.behaviors +6 -0
- data/lib/hecks/hecks/custodian.bluebook +217 -0
- data/lib/hecks/hecks/hecks.hecksagon +24 -0
- data/lib/hecks/hecks/hecks.world +8 -3
- data/lib/hecks/hecks/site.world +5 -2
- data/lib/hecks/language/bluebook/query.bluebook +25 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +213 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +0 -5
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings.rb +17 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +15 -2
- data/lib/hecks/projections/deploy/fargate.rb +3 -3
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/site/live_edge.rb +162 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
- data/lib/hecks/quality_control/quality_control.bluebook +76 -124
- data/lib/hecks/runtime/interpreting.rb +3 -22
- data/lib/hecks/runtime/loader.rb +6 -7
- data/lib/hecks/runtime/needs.rb +48 -0
- data/lib/hecks/runtime/query_interpreter.rb +3 -0
- data/lib/hecks/runtime/registry/verification.rb +3 -3
- data/lib/hecks/runtime.rb +6 -11
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +40 -18
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +3 -10
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +53 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +1 -3
- data/lib/hecks/tools/tools_doc.rb +204 -0
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +151 -0
- data/lib/hecks.rb +6 -12
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +30 -15
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +8 -19
- data/rust/codegen/src/bridging.rs +7 -7
- data/rust/codegen/src/json.rs +214 -23
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +20 -9
- data/rust/codegen/src/naming.rs +4 -2
- data/rust/codegen/src/optional_pass.rs +226 -0
- data/rust/{build → codegen}/src/sidecars.rs +9 -5
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +5 -0
- data/rust/host/src/needs.rs +99 -2
- data/rust/host/src/resend.rs +4 -4
- data/rust/host/src/ui_schema.rs +1 -1
- data/rust/parser/src/emit.rs +12 -0
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +2 -4
- data/rust/parser/src/lex.rs +1 -1
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/hecksagon.rs +15 -24
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/needs.rs +60 -0
- data/rust/parser/src/parse/query.rs +3 -0
- data/rust/src/kernel/cli.rs +16 -6
- data/rust/src/kernel/needs.rs +104 -9
- metadata +22 -9
- data/lib/hecks/facade.rb +0 -31
- data/lib/hecks/rust_build/append_optionals.rb +0 -79
- data/lib/hecks/rust_build/rust_literal.rb +0 -33
- data/lib/hecks/three_zero/forms.yml +0 -127
- data/lib/hecks/three_zero.rb +0 -12
- data/rust/build/src/json.rs +0 -421
- data/rust/build/src/optional_pass.rs +0 -150
|
@@ -164,7 +164,7 @@ pub fn parse_body(
|
|
|
164
164
|
owner_entities,
|
|
165
165
|
);
|
|
166
166
|
refuse_duplicate_targets(file, *pos, &command)?;
|
|
167
|
-
|
|
167
|
+
super::needs::refuse_undeclared(file, *pos, &command.name, &command.needs, &command.attributes)?;
|
|
168
168
|
return Ok((command, pending));
|
|
169
169
|
};
|
|
170
170
|
let line = gated.line.number;
|
|
@@ -206,27 +206,7 @@ pub fn parse_body(
|
|
|
206
206
|
canonical: canonical::apply(&raw),
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
|
-
"needs" =>
|
|
210
|
-
let fact = super::positional_symbol(file, line, "needs", &gated.args, 1)?;
|
|
211
|
-
if !NEEDABLE_FACTS.contains(&fact.as_str()) {
|
|
212
|
-
return Err(Diagnostic::new(
|
|
213
|
-
file,
|
|
214
|
-
line,
|
|
215
|
-
format!(
|
|
216
|
-
"{} needs :{fact}, which the runtime cannot supply — it supplies :now",
|
|
217
|
-
command.name
|
|
218
|
-
),
|
|
219
|
-
));
|
|
220
|
-
}
|
|
221
|
-
if command.needs.contains(&fact) {
|
|
222
|
-
return Err(Diagnostic::new(
|
|
223
|
-
file,
|
|
224
|
-
line,
|
|
225
|
-
format!("{} declares needs :{fact} twice", command.name),
|
|
226
|
-
));
|
|
227
|
-
}
|
|
228
|
-
command.needs.push(fact);
|
|
229
|
-
}
|
|
209
|
+
"needs" => super::needs::declare(file, line, &command.name, &gated.args, &mut command.needs)?,
|
|
230
210
|
"provenance" => {
|
|
231
211
|
let raw = super::named_raw(&gated.args, "from")
|
|
232
212
|
.ok_or_else(|| Diagnostic::new(file, line, "'provenance' requires a from:"))?;
|
|
@@ -319,28 +299,6 @@ fn state_ref(raw: &str) -> Option<String> {
|
|
|
319
299
|
Some(inner.to_string())
|
|
320
300
|
}
|
|
321
301
|
|
|
322
|
-
/// The outside facts a command may declare it needs; mirrors `CommandBuilder::NEEDABLE_FACTS`.
|
|
323
|
-
const NEEDABLE_FACTS: &[&str] = &["now"];
|
|
324
|
-
|
|
325
|
-
/// A need names the attribute the runtime fills, so the command must declare it.
|
|
326
|
-
fn refuse_undeclared_needs(file: &str, line: usize, command: &ir::Command) -> ParseResult<()> {
|
|
327
|
-
match command
|
|
328
|
-
.needs
|
|
329
|
-
.iter()
|
|
330
|
-
.find(|fact| !command.attributes.iter().any(|a| &a.name == *fact))
|
|
331
|
-
{
|
|
332
|
-
Some(fact) => Err(Diagnostic::new(
|
|
333
|
-
file,
|
|
334
|
-
line,
|
|
335
|
-
format!(
|
|
336
|
-
"{} needs :{fact} but declares no attribute :{fact} for the runtime to fill — add `attribute :{fact}, <type>`",
|
|
337
|
-
command.name
|
|
338
|
-
),
|
|
339
|
-
)),
|
|
340
|
-
None => Ok(()),
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
302
|
/// Refuses a field written twice: a command's effects are one update set (C4.2).
|
|
345
303
|
/// `delegates_to`/`corrects` name no field and are not counted.
|
|
346
304
|
fn refuse_duplicate_targets(file: &str, line: usize, command: &ir::Command) -> ParseResult<()> {
|
|
@@ -14,9 +14,8 @@ pub fn not_implemented(file: &str, line: usize, word: &str) -> Diagnostic {
|
|
|
14
14
|
/// Applies a `.hecksagon` body onto an already-built `ir::Bluebook`, attaching
|
|
15
15
|
/// aggregate-scoped ports to the aggregates the sibling `.bluebook` registered.
|
|
16
16
|
///
|
|
17
|
-
/// `gem_chapter_names` collects each `attaches "Name"`
|
|
18
|
-
/// `vendored_bluebook_names` each `attaches "name", from: :vendor`
|
|
19
|
-
/// `uses_embryonaut_bluebook`), in file order, for `hecks-parse resolve`; `ir.json` itself
|
|
17
|
+
/// `gem_chapter_names` collects each `attaches "Name"` and
|
|
18
|
+
/// `vendored_bluebook_names` each `attaches "name", from: :vendor`, in file order, for `hecks-parse resolve`; `ir.json` itself
|
|
20
19
|
/// carries neither.
|
|
21
20
|
///
|
|
22
21
|
/// `require_matching_aggregate` is `false` for resolve, which never sees a `.bluebook`, so an
|
|
@@ -65,8 +64,20 @@ pub fn apply(
|
|
|
65
64
|
if let LineShape::Call(call) = lex::classify(file, &line)? {
|
|
66
65
|
if !matches!(
|
|
67
66
|
call.word.as_str(),
|
|
68
|
-
"port" | "subscribe" | "
|
|
67
|
+
"port" | "subscribe" | "attaches" | "translates" | "bounded" | "end"
|
|
69
68
|
) {
|
|
69
|
+
let instead = match call.word.as_str() {
|
|
70
|
+
"uses_framework" => Some("attaches \"Name\""),
|
|
71
|
+
"uses_embryonaut_bluebook" => Some("attaches \"name\", from: :vendor"),
|
|
72
|
+
_ => None,
|
|
73
|
+
};
|
|
74
|
+
if let Some(instead) = instead {
|
|
75
|
+
return Err(Diagnostic::new(
|
|
76
|
+
file,
|
|
77
|
+
line.number,
|
|
78
|
+
format!("`{}` was removed in 3.4.0; use `{instead}`", call.word),
|
|
79
|
+
));
|
|
80
|
+
}
|
|
70
81
|
*pos += 1;
|
|
71
82
|
if matches!(call.opener, Opener::DoBlock { .. }) {
|
|
72
83
|
skip_dropped_body(file, lines, pos)?;
|
|
@@ -98,26 +109,6 @@ pub fn apply(
|
|
|
98
109
|
let built = policy::parse_body(file, lines, pos, &name)?;
|
|
99
110
|
bluebook.policies.push(built);
|
|
100
111
|
}
|
|
101
|
-
// Deprecated spelling of `attaches "Name"`; gated, then collected.
|
|
102
|
-
"uses_framework" => {
|
|
103
|
-
gem_chapter_names.push(super::positional_text(
|
|
104
|
-
file,
|
|
105
|
-
gated.line.number,
|
|
106
|
-
"uses_framework",
|
|
107
|
-
&gated.args,
|
|
108
|
-
1,
|
|
109
|
-
)?);
|
|
110
|
-
}
|
|
111
|
-
// Deprecated spelling of `attaches "name", from: :vendor`; collected the same way.
|
|
112
|
-
"uses_embryonaut_bluebook" => {
|
|
113
|
-
vendored_bluebook_names.push(super::positional_text(
|
|
114
|
-
file,
|
|
115
|
-
gated.line.number,
|
|
116
|
-
"uses_embryonaut_bluebook",
|
|
117
|
-
&gated.args,
|
|
118
|
-
1,
|
|
119
|
-
)?);
|
|
120
|
-
}
|
|
121
112
|
"subscribe" => {}
|
|
122
113
|
// One word for a chapter the gem carries and for a vendored package; a binding
|
|
123
114
|
// fact collected for `resolve`, absent from `ir.json`. Only `from: :vendor`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//! The `needs` word, shared by the two declarations that may use it: a command and a query
|
|
2
|
+
//! (ADR 0081). Mirrors `Hecks::Bluebook::DSL::NeedWord`.
|
|
3
|
+
|
|
4
|
+
use crate::diag::{Diagnostic, ParseResult};
|
|
5
|
+
use crate::ir;
|
|
6
|
+
use crate::parse::ArgumentGateResult;
|
|
7
|
+
|
|
8
|
+
/// The outside facts a declaration may need, each the name of the attribute the runtime fills;
|
|
9
|
+
/// mirrors `NeedWord::NEEDABLE_FACTS`. `now` is the clock port's reading in epoch seconds and
|
|
10
|
+
/// `today` the day it falls in, whole days since the epoch in UTC.
|
|
11
|
+
const NEEDABLE_FACTS: &[&str] = &["now", "today"];
|
|
12
|
+
|
|
13
|
+
/// Reads one `needs :fact` line into `needs`.
|
|
14
|
+
///
|
|
15
|
+
/// Refuses a fact the runtime cannot supply and a fact declared twice, as the Ruby builder does.
|
|
16
|
+
pub fn declare(
|
|
17
|
+
file: &str,
|
|
18
|
+
line: usize,
|
|
19
|
+
owner: &str,
|
|
20
|
+
args: &ArgumentGateResult,
|
|
21
|
+
needs: &mut Vec<String>,
|
|
22
|
+
) -> ParseResult<()> {
|
|
23
|
+
let fact = super::positional_symbol(file, line, "needs", args, 1)?;
|
|
24
|
+
if !NEEDABLE_FACTS.contains(&fact.as_str()) {
|
|
25
|
+
let supplied: Vec<String> = NEEDABLE_FACTS.iter().map(|known| format!(":{known}")).collect();
|
|
26
|
+
return Err(Diagnostic::new(
|
|
27
|
+
file,
|
|
28
|
+
line,
|
|
29
|
+
format!(
|
|
30
|
+
"{owner} needs :{fact}, which the runtime cannot supply — it supplies {}",
|
|
31
|
+
supplied.join(", ")
|
|
32
|
+
),
|
|
33
|
+
));
|
|
34
|
+
}
|
|
35
|
+
if needs.contains(&fact) {
|
|
36
|
+
return Err(Diagnostic::new(file, line, format!("{owner} declares needs :{fact} twice")));
|
|
37
|
+
}
|
|
38
|
+
needs.push(fact);
|
|
39
|
+
Ok(())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// A need names the attribute the runtime fills, so the declaration must have one.
|
|
43
|
+
pub fn refuse_undeclared(
|
|
44
|
+
file: &str,
|
|
45
|
+
line: usize,
|
|
46
|
+
owner: &str,
|
|
47
|
+
needs: &[String],
|
|
48
|
+
attributes: &[ir::Attribute],
|
|
49
|
+
) -> ParseResult<()> {
|
|
50
|
+
match needs.iter().find(|fact| !attributes.iter().any(|a| &a.name == *fact)) {
|
|
51
|
+
Some(fact) => Err(Diagnostic::new(
|
|
52
|
+
file,
|
|
53
|
+
line,
|
|
54
|
+
format!(
|
|
55
|
+
"{owner} needs :{fact} but declares no attribute :{fact} for the runtime to fill — add `attribute :{fact}, <type>`"
|
|
56
|
+
),
|
|
57
|
+
)),
|
|
58
|
+
None => Ok(()),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -25,6 +25,8 @@ pub fn parse_body(
|
|
|
25
25
|
|
|
26
26
|
loop {
|
|
27
27
|
let Some(gated) = super::next_line(file, lines, pos, "Query")? else {
|
|
28
|
+
let end = lines.get(pos.saturating_sub(1)).map_or(0, |l| l.number);
|
|
29
|
+
super::needs::refuse_undeclared(file, end, name, &query.needs, &query.attributes)?;
|
|
28
30
|
return Ok(query);
|
|
29
31
|
};
|
|
30
32
|
let line = gated.line.number;
|
|
@@ -83,6 +85,7 @@ pub fn parse_body(
|
|
|
83
85
|
type_name
|
|
84
86
|
});
|
|
85
87
|
}
|
|
88
|
+
"needs" => super::needs::declare(file, line, name, &gated.args, &mut query.needs)?,
|
|
86
89
|
word if OPTION_WORDS.contains(&word) => {
|
|
87
90
|
query_options::apply(file, line, word, &gated.args, &mut query.options)?
|
|
88
91
|
}
|
data/rust/src/kernel/cli.rs
CHANGED
|
@@ -18,6 +18,8 @@ pub fn run(input: &str) -> String {
|
|
|
18
18
|
|
|
19
19
|
// Optional `"needs"`: which commands need which outside facts (ADR 0081), passed by the host.
|
|
20
20
|
super::needs::install(parsed.get("needs"));
|
|
21
|
+
// Optional `"query_needs"`: which queries need which outside facts, filled the same way.
|
|
22
|
+
super::needs::install_queries(parsed.get("query_needs"));
|
|
21
23
|
// Optional `"defaults"`: each command's declared argument defaults, filled the same way.
|
|
22
24
|
super::needs::install_defaults(parsed.get("defaults"));
|
|
23
25
|
|
|
@@ -86,6 +88,14 @@ pub fn run(input: &str) -> String {
|
|
|
86
88
|
// A query step's `args` sit at the sibling `"args"` key, as for a command step.
|
|
87
89
|
let empty_args = Json::Object(vec![]);
|
|
88
90
|
let args = step.get("args").unwrap_or(&empty_args);
|
|
91
|
+
// What the caller offered is what the log echoes; a fact the query `needs` is answered
|
|
92
|
+
// for the run alone (ADR 0081), so the log holds no value that depends on the clock.
|
|
93
|
+
let offered = args;
|
|
94
|
+
let filled = match step.get("query") {
|
|
95
|
+
Some(Json::Str(question)) => super::needs::enrich_query(question, args, step.get("occurred_at").and_then(Json::as_str)),
|
|
96
|
+
_ => None,
|
|
97
|
+
};
|
|
98
|
+
let args = filled.as_ref().unwrap_or(args);
|
|
89
99
|
|
|
90
100
|
// A `"query"` step carries no verb and takes one of three shapes:
|
|
91
101
|
// - `"Domain::Aggregate.Query"`: a declared aggregate query, looked up in `QUERIES`.
|
|
@@ -106,7 +116,7 @@ pub fn run(input: &str) -> String {
|
|
|
106
116
|
let rows = Json::Array(rows);
|
|
107
117
|
query_results.push(Json::obj(vec![
|
|
108
118
|
("query", Json::Str(question.clone())),
|
|
109
|
-
("args",
|
|
119
|
+
("args", offered.clone()),
|
|
110
120
|
("rows", rows.clone()),
|
|
111
121
|
("reference_rows", rows),
|
|
112
122
|
]));
|
|
@@ -115,7 +125,7 @@ pub fn run(input: &str) -> String {
|
|
|
115
125
|
let message = refusal.to_string();
|
|
116
126
|
query_results.push(Json::obj(vec![
|
|
117
127
|
("query", Json::Str(question.clone())),
|
|
118
|
-
("args",
|
|
128
|
+
("args", offered.clone()),
|
|
119
129
|
("rows", Json::Null),
|
|
120
130
|
("error", Json::Str(message.clone())),
|
|
121
131
|
("reference_rows", Json::Null),
|
|
@@ -131,7 +141,7 @@ pub fn run(input: &str) -> String {
|
|
|
131
141
|
let rows = Json::Array(entries.into_iter().map(|(id, record)| repository::row_json(id, record)).collect());
|
|
132
142
|
query_results.push(Json::obj(vec![
|
|
133
143
|
("query", Json::Str(question.clone())),
|
|
134
|
-
("args",
|
|
144
|
+
("args", offered.clone()),
|
|
135
145
|
("rows", rows.clone()),
|
|
136
146
|
// Same as `rows`: one compiled path (named_query.rs).
|
|
137
147
|
("reference_rows", rows),
|
|
@@ -143,7 +153,7 @@ pub fn run(input: &str) -> String {
|
|
|
143
153
|
let message = refusal.to_string();
|
|
144
154
|
query_results.push(Json::obj(vec![
|
|
145
155
|
("query", Json::Str(question.clone())),
|
|
146
|
-
("args",
|
|
156
|
+
("args", offered.clone()),
|
|
147
157
|
("rows", Json::Null),
|
|
148
158
|
("error", Json::Str(message.clone())),
|
|
149
159
|
("reference_rows", Json::Null),
|
|
@@ -170,7 +180,7 @@ pub fn run(input: &str) -> String {
|
|
|
170
180
|
Ok(row) => {
|
|
171
181
|
query_results.push(Json::obj(vec![
|
|
172
182
|
("query", Json::Str(question.clone())),
|
|
173
|
-
("args",
|
|
183
|
+
("args", offered.clone()),
|
|
174
184
|
("rows", Json::Array(vec![row])),
|
|
175
185
|
// No `reference_rows` key: Ruby sets it only for questions with a
|
|
176
186
|
// reference twin, and a read model has none.
|
|
@@ -181,7 +191,7 @@ pub fn run(input: &str) -> String {
|
|
|
181
191
|
let message = refusal.to_string();
|
|
182
192
|
query_results.push(Json::obj(vec![
|
|
183
193
|
("query", Json::Str(question.clone())),
|
|
184
|
-
("args",
|
|
194
|
+
("args", offered.clone()),
|
|
185
195
|
("rows", Json::Null),
|
|
186
196
|
("error", Json::Str(message)),
|
|
187
197
|
]));
|
data/rust/src/kernel/needs.rs
CHANGED
|
@@ -26,15 +26,16 @@ pub struct Need {
|
|
|
26
26
|
thread_local! {
|
|
27
27
|
static TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
|
|
28
28
|
static DEFAULTS: RefCell<HashMap<String, Vec<(String, Json)>>> = RefCell::new(HashMap::new());
|
|
29
|
+
static QUERY_TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
|
|
29
30
|
static FIXED_CLOCK: Cell<Option<i64>> = const { Cell::new(None) };
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
///
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
/// Reads a `{ name: [{fact, type}] }` table (an absent or malformed one reads as empty).
|
|
34
|
+
fn parse_table(table: Option<&Json>) -> HashMap<String, Vec<Need>> {
|
|
35
|
+
match table {
|
|
35
36
|
Some(Json::Object(entries)) => entries
|
|
36
37
|
.iter()
|
|
37
|
-
.map(|(
|
|
38
|
+
.map(|(name, needs)| {
|
|
38
39
|
let needs = needs
|
|
39
40
|
.as_array()
|
|
40
41
|
.unwrap_or(&[])
|
|
@@ -45,12 +46,23 @@ pub fn install(table: Option<&Json>) {
|
|
|
45
46
|
Some(Need { fact, attribute_type })
|
|
46
47
|
})
|
|
47
48
|
.collect();
|
|
48
|
-
(
|
|
49
|
+
(name.clone(), needs)
|
|
49
50
|
})
|
|
50
51
|
.collect(),
|
|
51
52
|
_ => HashMap::new(),
|
|
52
|
-
}
|
|
53
|
-
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Installs the table the host passed, command verb to its needs (an absent or malformed
|
|
57
|
+
/// `"needs"` installs an empty one).
|
|
58
|
+
pub fn install(table: Option<&Json>) {
|
|
59
|
+
TABLE.with(|t| *t.borrow_mut() = parse_table(table));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Installs the table of queries that need a fact, `"Domain::Aggregate.Query"` to its needs. Kept
|
|
63
|
+
/// apart from the commands': a command and a query may share a qualified name.
|
|
64
|
+
pub fn install_queries(table: Option<&Json>) {
|
|
65
|
+
QUERY_TABLE.with(|t| *t.borrow_mut() = parse_table(table));
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
/// Installs the declared defaults the host passed, `{ verb: { attribute: value } }` (an absent or
|
|
@@ -106,9 +118,11 @@ fn parse_iso8601_utc(text: &str) -> Option<i64> {
|
|
|
106
118
|
|
|
107
119
|
fn answer(need: &Need, occurred_at: Option<&str>) -> Option<Json> {
|
|
108
120
|
match need.fact.as_str() {
|
|
109
|
-
|
|
121
|
+
// `now` is epoch seconds; `today` the day those fall in, whole days since the epoch (UTC).
|
|
122
|
+
"now" | "today" => {
|
|
110
123
|
let secs = now_secs(occurred_at);
|
|
111
|
-
|
|
124
|
+
let answer = if need.fact == "today" { secs.div_euclid(86_400) } else { secs };
|
|
125
|
+
Some(if need.attribute_type == "Integer" { Json::int(answer) } else { Json::obj(vec![("value", Json::int(answer))]) })
|
|
112
126
|
}
|
|
113
127
|
_ => None,
|
|
114
128
|
}
|
|
@@ -151,6 +165,27 @@ pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json
|
|
|
151
165
|
Some(Json::Object(envelope.iter().map(|(k, v)| if k == "with" { (k.clone(), Json::Object(filled.clone())) } else { (k.clone(), v.clone()) }).collect()))
|
|
152
166
|
}
|
|
153
167
|
|
|
168
|
+
/// `args` with each fact the query `question` needs and the caller left out answered, or `None`
|
|
169
|
+
/// when nothing needed filling (the query needs nothing, or every fact is supplied). A query's
|
|
170
|
+
/// arguments are the flat object of its step.
|
|
171
|
+
pub fn enrich_query(question: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json> {
|
|
172
|
+
let needs = QUERY_TABLE.with(|t| t.borrow().get(question).cloned())?;
|
|
173
|
+
let Json::Object(held) = args else { return None };
|
|
174
|
+
let mut filled = held.clone();
|
|
175
|
+
for need in &needs {
|
|
176
|
+
if held.iter().any(|(k, _)| k == &need.fact) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if let Some(value) = answer(need, occurred_at) {
|
|
180
|
+
filled.push((need.fact.clone(), value));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if filled.len() == held.len() {
|
|
184
|
+
return None;
|
|
185
|
+
}
|
|
186
|
+
Some(Json::Object(filled))
|
|
187
|
+
}
|
|
188
|
+
|
|
154
189
|
#[cfg(test)]
|
|
155
190
|
mod tests {
|
|
156
191
|
use super::*;
|
|
@@ -264,4 +299,64 @@ mod tests {
|
|
|
264
299
|
install_defaults(None);
|
|
265
300
|
assert_eq!(enrich("D::A.Start", &with(vec![]), None), None);
|
|
266
301
|
}
|
|
302
|
+
|
|
303
|
+
fn query_table() -> Json {
|
|
304
|
+
Json::parse(
|
|
305
|
+
r#"{"D::A.Expired": [{"fact": "now", "type": "Integer"}],
|
|
306
|
+
"D::A.Today": [{"fact": "today", "type": "DayNumber"}]}"#,
|
|
307
|
+
)
|
|
308
|
+
.unwrap()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn a_needed_query_fact_the_caller_left_out_is_answered_by_the_clock() {
|
|
313
|
+
install_queries(Some(&query_table()));
|
|
314
|
+
fix_clock(Some(1_700_000_000));
|
|
315
|
+
let filled = enrich_query("D::A.Expired", &Json::obj(vec![]), None).unwrap();
|
|
316
|
+
assert_eq!(filled.get("now"), Some(&Json::int(1_700_000_000)));
|
|
317
|
+
fix_clock(None);
|
|
318
|
+
install_queries(None);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#[test]
|
|
322
|
+
fn a_query_value_the_caller_supplied_is_kept() {
|
|
323
|
+
install_queries(Some(&query_table()));
|
|
324
|
+
fix_clock(Some(1));
|
|
325
|
+
assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![("now", Json::int(7))]), None), None);
|
|
326
|
+
fix_clock(None);
|
|
327
|
+
install_queries(None);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#[test]
|
|
331
|
+
fn a_query_that_needs_nothing_is_untouched() {
|
|
332
|
+
install_queries(Some(&query_table()));
|
|
333
|
+
assert_eq!(enrich_query("D::A.Plain", &Json::obj(vec![]), None), None);
|
|
334
|
+
install_queries(None);
|
|
335
|
+
assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![]), None), None);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[test]
|
|
339
|
+
fn a_command_and_a_query_of_one_name_keep_their_own_needs() {
|
|
340
|
+
install(Some(&table()));
|
|
341
|
+
install_queries(Some(&query_table()));
|
|
342
|
+
fix_clock(Some(5));
|
|
343
|
+
assert_eq!(enrich_query("D::A.Instant", &Json::obj(vec![]), None), None);
|
|
344
|
+
assert!(enrich("D::A.Instant", &with(vec![]), None).is_some());
|
|
345
|
+
fix_clock(None);
|
|
346
|
+
install(None);
|
|
347
|
+
install_queries(None);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn today_is_the_day_the_clock_falls_in_whole_days_since_the_epoch() {
|
|
352
|
+
install_queries(Some(&query_table()));
|
|
353
|
+
fix_clock(Some((19_000 * 86_400) + 86_399));
|
|
354
|
+
let filled = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
|
|
355
|
+
assert_eq!(filled.get("today"), Some(&Json::obj(vec![("value", Json::int(19_000))])));
|
|
356
|
+
fix_clock(Some(19_001 * 86_400));
|
|
357
|
+
let next = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
|
|
358
|
+
assert_eq!(next.get("today"), Some(&Json::obj(vec![("value", Json::int(19_001))])));
|
|
359
|
+
fix_clock(None);
|
|
360
|
+
install_queries(None);
|
|
361
|
+
}
|
|
267
362
|
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
@@ -162,6 +162,7 @@ files:
|
|
|
162
162
|
- lib/hecks/bluebook/dsl/identity_declaration.rb
|
|
163
163
|
- lib/hecks/bluebook/dsl/lifecycle_builder.rb
|
|
164
164
|
- lib/hecks/bluebook/dsl/malformed.rb
|
|
165
|
+
- lib/hecks/bluebook/dsl/need_word.rb
|
|
165
166
|
- lib/hecks/bluebook/dsl/policy_builder.rb
|
|
166
167
|
- lib/hecks/bluebook/dsl/port_builder.rb
|
|
167
168
|
- lib/hecks/bluebook/dsl/port_operation_builder.rb
|
|
@@ -232,6 +233,7 @@ files:
|
|
|
232
233
|
- lib/hecks/cli/interview_draft.rb
|
|
233
234
|
- lib/hecks/cli/interview_run.rb
|
|
234
235
|
- lib/hecks/cli/interview_session.rb
|
|
236
|
+
- lib/hecks/cli/interview_shape.rb
|
|
235
237
|
- lib/hecks/cli/ir.rb
|
|
236
238
|
- lib/hecks/cli/mcp.rb
|
|
237
239
|
- lib/hecks/cli/model_check.rb
|
|
@@ -280,11 +282,12 @@ files:
|
|
|
280
282
|
- lib/hecks/doors/usage_cache.rb
|
|
281
283
|
- lib/hecks/embryonaut_bluebook.rb
|
|
282
284
|
- lib/hecks/embryonaut_bluebook/lock.rb
|
|
285
|
+
- lib/hecks/embryonaut_bluebook/manifest.rb
|
|
286
|
+
- lib/hecks/embryonaut_bluebook/registry.rb
|
|
283
287
|
- lib/hecks/embryonaut_bluebook/shape.rb
|
|
284
288
|
- lib/hecks/embryonaut_bluebook/vendor.rb
|
|
285
289
|
- lib/hecks/embryonaut_bluebook/vendor_cli.rb
|
|
286
290
|
- lib/hecks/engine_agreement.rb
|
|
287
|
-
- lib/hecks/facade.rb
|
|
288
291
|
- lib/hecks/forms.rb
|
|
289
292
|
- lib/hecks/forms/app.rb
|
|
290
293
|
- lib/hecks/forms/banking_presentation.rb
|
|
@@ -387,10 +390,12 @@ files:
|
|
|
387
390
|
- lib/hecks/hecks/adapters/deploy_toolchain.rb
|
|
388
391
|
- lib/hecks/hecks/adapters/finding_github.adapter
|
|
389
392
|
- lib/hecks/hecks/adapters/finding_github.rb
|
|
393
|
+
- lib/hecks/hecks/adapters/first_admin.rb
|
|
390
394
|
- lib/hecks/hecks/adapters/git.adapter
|
|
391
395
|
- lib/hecks/hecks/adapters/git.rb
|
|
392
396
|
- lib/hecks/hecks/adapters/host_http.adapter
|
|
393
397
|
- lib/hecks/hecks/adapters/host_http.rb
|
|
398
|
+
- lib/hecks/hecks/adapters/in_process_access.rb
|
|
394
399
|
- lib/hecks/hecks/adapters/in_process_boot.adapter
|
|
395
400
|
- lib/hecks/hecks/adapters/in_process_boot.rb
|
|
396
401
|
- lib/hecks/hecks/adapters/in_process_operations.rb
|
|
@@ -546,14 +551,22 @@ files:
|
|
|
546
551
|
- lib/hecks/projections.rb
|
|
547
552
|
- lib/hecks/projections/bootstrap_table.rb
|
|
548
553
|
- lib/hecks/projections/deploy/box.rb
|
|
554
|
+
- lib/hecks/projections/deploy/box/hosting.rb
|
|
555
|
+
- lib/hecks/projections/deploy/box/hosting_settings.rb
|
|
549
556
|
- lib/hecks/projections/deploy/box/settings.rb
|
|
550
557
|
- lib/hecks/projections/deploy/box/templates/box.yaml.tmpl
|
|
551
558
|
- lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl
|
|
559
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part
|
|
560
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl
|
|
561
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part
|
|
562
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part
|
|
552
563
|
- lib/hecks/projections/deploy/box/templates/fetch-secrets.sh
|
|
564
|
+
- lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl
|
|
553
565
|
- lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl
|
|
554
566
|
- lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl
|
|
555
567
|
- lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl
|
|
556
568
|
- lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl
|
|
569
|
+
- lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl
|
|
557
570
|
- lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl
|
|
558
571
|
- lib/hecks/projections/deploy/fargate.rb
|
|
559
572
|
- lib/hecks/projections/deploy/fargate/assembly.rb
|
|
@@ -612,6 +625,7 @@ files:
|
|
|
612
625
|
- lib/hecks/projections/site/edge/checks.rb
|
|
613
626
|
- lib/hecks/projections/site/edge/pattern.rb
|
|
614
627
|
- lib/hecks/projections/site/edge/rules.rb
|
|
628
|
+
- lib/hecks/projections/site/live_edge.rb
|
|
615
629
|
- lib/hecks/projections/site/regions.rb
|
|
616
630
|
- lib/hecks/projections/site/routes_ts.rb
|
|
617
631
|
- lib/hecks/projections/site/site_cdn.rb
|
|
@@ -734,6 +748,7 @@ files:
|
|
|
734
748
|
- lib/hecks/runtime/interpreting.rb
|
|
735
749
|
- lib/hecks/runtime/invocation.rb
|
|
736
750
|
- lib/hecks/runtime/loader.rb
|
|
751
|
+
- lib/hecks/runtime/needs.rb
|
|
737
752
|
- lib/hecks/runtime/outbox.rb
|
|
738
753
|
- lib/hecks/runtime/policy_interpreter.rb
|
|
739
754
|
- lib/hecks/runtime/port_operation_interpreter.rb
|
|
@@ -762,17 +777,16 @@ files:
|
|
|
762
777
|
- lib/hecks/runtime/value/field_checks.rb
|
|
763
778
|
- lib/hecks/runtime/value/invariant_violation.rb
|
|
764
779
|
- lib/hecks/rust_build.rb
|
|
765
|
-
- lib/hecks/rust_build/append_optionals.rb
|
|
766
780
|
- lib/hecks/rust_build/conformance.rb
|
|
767
781
|
- lib/hecks/rust_build/conformance_fuzz.rb
|
|
768
782
|
- lib/hecks/rust_build/coverage.rb
|
|
769
783
|
- lib/hecks/rust_build/domain_name.rb
|
|
784
|
+
- lib/hecks/rust_build/host.rb
|
|
770
785
|
- lib/hecks/rust_build/kernel_input.rb
|
|
771
786
|
- lib/hecks/rust_build/native_build.rb
|
|
772
787
|
- lib/hecks/rust_build/project_rust.rb
|
|
773
788
|
- lib/hecks/rust_build/project_rust/codegen_run.rb
|
|
774
789
|
- lib/hecks/rust_build/project_rust/root_mod.rb
|
|
775
|
-
- lib/hecks/rust_build/rust_literal.rb
|
|
776
790
|
- lib/hecks/rust_build/wasm.rb
|
|
777
791
|
- lib/hecks/rust_build/wasm_browser.rb
|
|
778
792
|
- lib/hecks/rust_build/write_if_changed.rb
|
|
@@ -784,8 +798,6 @@ files:
|
|
|
784
798
|
- lib/hecks/storehouse.rb
|
|
785
799
|
- lib/hecks/tenancy/bluebook/tenancy.bluebook
|
|
786
800
|
- lib/hecks/tenancy/bluebook/tenancy.hecksagon
|
|
787
|
-
- lib/hecks/three_zero.rb
|
|
788
|
-
- lib/hecks/three_zero/forms.yml
|
|
789
801
|
- lib/hecks/tickets/bluebook/tickets.bluebook
|
|
790
802
|
- lib/hecks/tickets/bluebook/tickets.hecksagon
|
|
791
803
|
- lib/hecks/tickets/bluebook/tickets.ports.hecksagon
|
|
@@ -814,6 +826,7 @@ files:
|
|
|
814
826
|
- lib/hecks/tools/site_routes.rb
|
|
815
827
|
- lib/hecks/tools/tail_merge.rb
|
|
816
828
|
- lib/hecks/tools/tenant_provisioning.rb
|
|
829
|
+
- lib/hecks/tools/tools_doc.rb
|
|
817
830
|
- lib/hecks/tools/translation_audit.rb
|
|
818
831
|
- lib/hecks/tools/translation_scaffold.rb
|
|
819
832
|
- lib/hecks/vendoring.rb
|
|
@@ -835,15 +848,12 @@ files:
|
|
|
835
848
|
- rust/build/Cargo.toml
|
|
836
849
|
- rust/build/src/build_artifact.rs
|
|
837
850
|
- rust/build/src/cargo_sync.rs
|
|
838
|
-
- rust/build/src/json.rs
|
|
839
851
|
- rust/build/src/lineage_pass.rs
|
|
840
852
|
- rust/build/src/main.rs
|
|
841
|
-
- rust/build/src/optional_pass.rs
|
|
842
853
|
- rust/build/src/pipeline.rs
|
|
843
854
|
- rust/build/src/reserved_names.rs
|
|
844
855
|
- rust/build/src/resolve.rs
|
|
845
856
|
- rust/build/src/root.rs
|
|
846
|
-
- rust/build/src/sidecars.rs
|
|
847
857
|
- rust/build/src/subprocess.rs
|
|
848
858
|
- rust/build/src/tmp.rs
|
|
849
859
|
- rust/codegen/.cargo/config.toml
|
|
@@ -867,6 +877,7 @@ files:
|
|
|
867
877
|
- rust/codegen/src/manifest.rs
|
|
868
878
|
- rust/codegen/src/mutations.rs
|
|
869
879
|
- rust/codegen/src/naming.rs
|
|
880
|
+
- rust/codegen/src/optional_pass.rs
|
|
870
881
|
- rust/codegen/src/ports.rs
|
|
871
882
|
- rust/codegen/src/prelude.rs
|
|
872
883
|
- rust/codegen/src/queries.rs
|
|
@@ -876,6 +887,7 @@ files:
|
|
|
876
887
|
- rust/codegen/src/registry.rs
|
|
877
888
|
- rust/codegen/src/reserved_names.rs
|
|
878
889
|
- rust/codegen/src/shared.rs
|
|
890
|
+
- rust/codegen/src/sidecars.rs
|
|
879
891
|
- rust/codegen/src/skip_reason.rs
|
|
880
892
|
- rust/codegen/src/types.rs
|
|
881
893
|
- rust/host/.cargo/config.toml
|
|
@@ -969,6 +981,7 @@ files:
|
|
|
969
981
|
- rust/parser/src/parse/hecksagon.rs
|
|
970
982
|
- rust/parser/src/parse/lifecycle.rs
|
|
971
983
|
- rust/parser/src/parse/mod.rs
|
|
984
|
+
- rust/parser/src/parse/needs.rs
|
|
972
985
|
- rust/parser/src/parse/policy.rs
|
|
973
986
|
- rust/parser/src/parse/process_manager.rs
|
|
974
987
|
- rust/parser/src/parse/query.rs
|
data/lib/hecks/facade.rb
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# The deprecated alias shim: `Hecks::Facade` is now `Hecks::Doors` and `Doors::Surface` is
|
|
2
|
-
# `Doors::RubyDoor`. Both old constants still resolve and warn; they are removed in
|
|
3
|
-
# `Hecks::Doors::REMOVAL`.
|
|
4
|
-
require_relative "doors"
|
|
5
|
-
|
|
6
|
-
# The root namespace; this file holds only the deprecated aliases.
|
|
7
|
-
module Hecks
|
|
8
|
-
# Resolves the constants that now live in `Doors` under their old names, warning on each use.
|
|
9
|
-
#
|
|
10
|
-
# Extended onto `Hecks` and `Hecks::Doors`, ahead of `Module#const_missing`.
|
|
11
|
-
module DoorsAliases
|
|
12
|
-
# Old constant name mapped to the new one, per namespace that carried it.
|
|
13
|
-
RENAMED = { Hecks => { Facade: "Hecks::Doors" }, Hecks::Doors => { Surface: "Hecks::Doors::RubyDoor" } }.freeze
|
|
14
|
-
|
|
15
|
-
# Answers a renamed constant with its replacement and a warning; anything else
|
|
16
|
-
# goes to the next `const_missing`.
|
|
17
|
-
#
|
|
18
|
-
# @param name [Symbol] the missing constant
|
|
19
|
-
# @return [Module] the replacement module
|
|
20
|
-
def const_missing(name)
|
|
21
|
-
target = RENAMED.fetch(self, {})[name]
|
|
22
|
-
return super unless target
|
|
23
|
-
|
|
24
|
-
warn "[hecks] #{self}::#{name} is deprecated and is removed in #{Hecks::Doors::REMOVAL}; use #{target}"
|
|
25
|
-
Object.const_get(target)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
extend DoorsAliases
|
|
30
|
-
Doors.extend(DoorsAliases)
|
|
31
|
-
end
|