Leaf: a run kind, PipelineRun with RunOperator and RunTurn, instance-rooted
A run is the one record of what a Self or a Persona turn took on: key <instance>:run:<label>, claims of CutSpec, Finding or FollowUp only (refused at validate), a canonical-decimal budget, turn PersonaTurn or SelfRun per ruling every-wake-a-run. The run sample is appended at the end of samples(); the golden gains one line after sample.hand_off and no existing line changes. The schema and its catalogue entry are derived from the type. No existing kind or field is touched; the epoch stays v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f0df28ece1
commit
6c705f5f2a
5 changed files with 287 additions and 5 deletions
|
|
@ -22,6 +22,7 @@ sample.resolution eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resol
|
|||
sample.instance yggdrasil:instance:self epiphany.pipeline.instance.v2 EpiphanyPipelineInstanceDocument 9184a8696e7374616e6365a979676764726173696cac646973706c61795f6e616d65ae59676764726173696c206d696e64aa637265617465645f6174aa323032362d30392d3135a4686f7374a979676764726173696c
|
||||
sample.stewardship yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline.stewardship.v2 EpiphanyPipelineStewardshipDocument 9185a8696e7374616e6365a979676764726173696ca47265706fb147616d6543756c742f4570697068616e79a873657175656e636501ab61737369676e65645f6f6eaa323032362d30392d3135a46e6f7465b954686520457572656b612063616d706169676e207265706f2e
|
||||
sample.hand_off yggdrasil:hand_off:thought-cage.gamecult_-epiphany.2026-09-15 epiphany.pipeline.hand_off.v2 EpiphanyPipelineHandOffDocument 9186ad66726f6d5f696e7374616e6365a979676764726173696cab746f5f696e7374616e6365ac74686f756768742d63616765a47265706fb147616d6543756c742f4570697068616e79a9646f63756d656e747392ac657572656b612d7374617465b6657572656b612d73746174653a72756c696e673a5238a6726561736f6ed92854686520776f726b73746174696f6e206d696e642074616b6573207468652063616d706169676e2ea968616e6465645f6f6eaa323032362d30392d3135
|
||||
sample.run yggdrasil:run:mind-2026-10-09-1 epiphany.pipeline.run.v2 EpiphanyPipelineRunDocument 9189a8696e7374616e6365a979676764726173696ca56c6162656cb16d696e642d323032362d31302d30392d31ab6f706572617465645f6279a44d696e64a47475726ea753656c6652756ea4686f7374a979676764726173696caa737461727465645f6f6eaa323032362d30392d3135aa6275646765745f757364a3372e35a6636c61696d739182a46b696e64a743757453706563a26964d925657572656b612d73746174653a6375745f737065633a6375742d72756e2d6b696e642e7232a963616d706169676e7391ac657572656b612d7374617465
|
||||
resolution.superseded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581aa5375706572736564656481a262799282a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a523882a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-none eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a26279c0a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-some eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a2627982a46b696e64a94375745265706f7274a26964d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
|
|
|
|||
|
|
@ -472,6 +472,28 @@ fn campaign_repos_are_distinct(campaign: &PipelineCampaign, at: &str) -> Result<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// A run claims work, never runs, rulings or campaigns: its claims are cut
|
||||
/// specs, findings and follow-ups, the three kinds a Self takes on. And its
|
||||
/// budget is a canonical decimal (`0`, `12`, `7.5`: no sign, no leading or
|
||||
/// trailing zeros, no exponent), so one spend has one spelling.
|
||||
fn run_is_well_formed(run: &PipelineRun, at: &str) -> Result<(), PipelineRefusal> {
|
||||
for (index, claim) in run.claims.iter().enumerate() {
|
||||
if !matches!(claim.kind, PipelineKind::CutSpec | PipelineKind::Finding | PipelineKind::FollowUp) {
|
||||
return Err(format_error(&format!("{at}.claims[{index}].kind"), claim.kind.name()));
|
||||
}
|
||||
}
|
||||
let (whole, fraction) = run.budget_usd.0.split_once('.').map_or((run.budget_usd.0.as_str(), None), |(whole, fraction)| (whole, Some(fraction)));
|
||||
let digits = |text: &str| !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit());
|
||||
let canonical = digits(whole)
|
||||
&& (whole == "0" || !whole.starts_with('0'))
|
||||
&& fraction.is_none_or(|fraction| digits(fraction) && !fraction.ends_with('0'));
|
||||
if canonical {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format_error(&format!("{at}.budget_usd"), &run.budget_usd.0))
|
||||
}
|
||||
}
|
||||
|
||||
unit_enums! {
|
||||
EvidenceKind { Command, Test, Mutation, Probe, SourceRead, Capture }
|
||||
ClaimOutcome { Holds, Falsified, Unproven }
|
||||
|
|
@ -479,6 +501,8 @@ unit_enums! {
|
|||
FindingSeverity { Blocker, High, Medium, Low }
|
||||
RulingAuthority { Operator, Standing, Defaulted, Mind }
|
||||
FindingOrigin { Introduced, PreExisting }
|
||||
RunOperator { Operator, Mind }
|
||||
RunTurn { PersonaTurn, SelfRun }
|
||||
}
|
||||
|
||||
value_types! {
|
||||
|
|
@ -595,6 +619,15 @@ value_types! {
|
|||
from_instance: Slug, to_instance: Slug, repo: OrgRepo, documents: Vec<Short>[256],
|
||||
reason: Para, handed_on: Date,
|
||||
}
|
||||
/// One launch of a Self or a Persona turn: the one record of what it took
|
||||
/// on. `claims` are the work it holds, and a claim is live while its run
|
||||
/// has no resolution in force. `budget_usd` is a canonical decimal string,
|
||||
/// `0` for an interactive run. Instance-rooted like stewardship: the key
|
||||
/// is `<instance>:run:<label>`. Whether a claim is free is admission's rule.
|
||||
pub struct PipelineRun {
|
||||
instance: Slug, label: Label, operated_by: RunOperator, turn: RunTurn, host: Short, started_on: Date,
|
||||
budget_usd: Short, claims: Vec<PipelineRef>[64], campaigns: Vec<Slug>[8],
|
||||
} => run_is_well_formed
|
||||
}
|
||||
|
||||
/// A reference to another document. The id is parsed as a full pipeline id of
|
||||
|
|
@ -697,7 +730,7 @@ macro_rules! pipeline_kinds {
|
|||
/// because a value is a named map of its own and an internal tag would
|
||||
/// have to be merged into it; the tag stays beside the value instead.
|
||||
/// This is not a published schema: `schemas/cultnet` publishes the
|
||||
/// thirteen per-kind value documents, and the envelope is the wire's
|
||||
/// fourteen per-kind value documents, and the envelope is the wire's
|
||||
/// shape, not a document's.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
|
||||
|
|
@ -813,6 +846,8 @@ pipeline_kinds! {
|
|||
"epiphany.pipeline.stewardship.v2", "EpiphanyPipelineStewardshipDocument";
|
||||
HandOff(PipelineHandOff) => EpiphanyPipelineHandOffDocument, "hand_off",
|
||||
"epiphany.pipeline.hand_off.v2", "EpiphanyPipelineHandOffDocument";
|
||||
Run(PipelineRun) => EpiphanyPipelineRunDocument, "run",
|
||||
"epiphany.pipeline.run.v2", "EpiphanyPipelineRunDocument";
|
||||
}
|
||||
|
||||
/// Parses a full document id: the grammar read backwards. A key and an id are
|
||||
|
|
@ -952,7 +987,7 @@ fn local(field: &str, kind: PipelineKind, parts: &[&str]) -> Result<String, Pipe
|
|||
}
|
||||
|
||||
/// Derives a document's identity key (D1, "Keys: identity, not convenience").
|
||||
/// Thirteen arms, one exit: every arm names its root and composes its local
|
||||
/// Fourteen arms, one exit: every arm names its root and composes its local
|
||||
/// through `local`, and the key is formatted here and nowhere else.
|
||||
pub fn pipeline_key(document: &PipelineDocument) -> Result<String, PipelineRefusal> {
|
||||
use PipelineDocument as D;
|
||||
|
|
@ -1005,6 +1040,8 @@ pub fn pipeline_key(document: &PipelineDocument) -> Result<String, PipelineRefus
|
|||
)?,
|
||||
)
|
||||
}
|
||||
// A run hangs off its instance too: its label is its whole local.
|
||||
D::Run(value) => ("run.instance", value.instance.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
D::Target(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&format!("r{}", value.revision)])?),
|
||||
D::Question(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
D::Ruling(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
|
|
@ -1242,6 +1279,12 @@ mod tests {
|
|||
documents: vec![s(CAMPAIGN), id("ruling", "R8")],
|
||||
reason: "The workstation mind takes the campaign.".into(), handed_on: date(),
|
||||
}), format!("{INSTANCE}:hand_off:thought-cage.gamecult_-epiphany.{}", date().0)),
|
||||
(D::Run(PipelineRun {
|
||||
instance: slug(INSTANCE), label: l("mind-2026-10-09-1"), operated_by: RunOperator::Mind, turn: RunTurn::SelfRun,
|
||||
host: s("yggdrasil"), started_on: date(), budget_usd: s("7.5"),
|
||||
claims: vec![PipelineRef { kind: PipelineKind::CutSpec, id: id("cut_spec", "cut-run-kind.r2") }],
|
||||
campaigns: vec![slug(CAMPAIGN)],
|
||||
}), format!("{INSTANCE}:run:mind-2026-10-09-1")),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1429,6 +1472,107 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_sample() -> PipelineRun {
|
||||
let PipelineDocument::Run(run) = samples().remove(13).0 else { unreachable!() };
|
||||
run
|
||||
}
|
||||
|
||||
fn run_key(run: PipelineRun) -> Result<String, PipelineRefusal> {
|
||||
pipeline_key(&PipelineDocument::Run(run))
|
||||
}
|
||||
|
||||
/// Identity: a run is its instance and its label, and no other field moves
|
||||
/// its key, so two launches under one label are one key.
|
||||
#[test]
|
||||
fn a_run_keys_off_its_instance_and_label_and_nothing_else() -> Result<()> {
|
||||
let base = run_sample();
|
||||
let key = run_key(base.clone())?;
|
||||
assert_eq!(key, format!("{INSTANCE}:run:mind-2026-10-09-1"));
|
||||
let mut varied = base.clone();
|
||||
varied.operated_by = RunOperator::Operator;
|
||||
varied.turn = RunTurn::PersonaTurn;
|
||||
varied.host = s("starfire");
|
||||
varied.started_on = Date("2026-10-10".into());
|
||||
varied.budget_usd = s("0");
|
||||
varied.claims = vec![];
|
||||
varied.campaigns = vec![];
|
||||
assert_eq!(run_key(varied)?, key, "no other field is part of the key");
|
||||
let mut relabelled = base.clone();
|
||||
relabelled.label = l("mind-2026-10-09-2");
|
||||
assert_ne!(run_key(relabelled)?, key);
|
||||
let mut moved = base;
|
||||
moved.instance = slug("thought-cage");
|
||||
assert_ne!(run_key(moved)?, key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Claims name work: a cut spec, a finding or a follow-up, and every other
|
||||
/// kind is refused at validate, whatever its id.
|
||||
#[test]
|
||||
fn a_run_claims_only_a_cut_spec_a_finding_or_a_follow_up() {
|
||||
for kind in PipelineKind::ALL.iter().copied() {
|
||||
let mut run = run_sample();
|
||||
run.claims = vec![PipelineRef { kind, id: id(kind.name(), "x") }];
|
||||
let outcome = PipelineDocument::Run(run).validate();
|
||||
if matches!(kind, PipelineKind::CutSpec | PipelineKind::Finding | PipelineKind::FollowUp) {
|
||||
assert_eq!(outcome, Ok(()), "{kind:?} is claimable");
|
||||
} else {
|
||||
assert!(
|
||||
matches!(&outcome, Err(PipelineRefusal::InvalidFormat { field, value }) if field == "run.claims[0].kind" && value == kind.name()),
|
||||
"{kind:?} is not claimable, got {outcome:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The budget has one spelling per amount.
|
||||
#[test]
|
||||
fn a_runs_budget_is_a_canonical_decimal() {
|
||||
let validated = |budget: &str| {
|
||||
let mut run = run_sample();
|
||||
run.budget_usd = s(budget);
|
||||
PipelineDocument::Run(run).validate()
|
||||
};
|
||||
for good in ["0", "1", "12", "7.5", "0.25", "100.001"] {
|
||||
assert_eq!(validated(good), Ok(()), "{good}");
|
||||
}
|
||||
for bad in ["", "00", "01", "1.", "1.0", "7.50", "0.0", ".5", "-1", "+1", "1e3", "1,5", " 1", "1 ", "1.2.3"] {
|
||||
assert!(
|
||||
matches!(validated(bad), Err(PipelineRefusal::InvalidFormat { field, .. }) if field == "run.budget_usd"),
|
||||
"{bad:?} is refused"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bounds: a run's campaigns and claims stop at their maximums.
|
||||
#[test]
|
||||
fn a_runs_lists_are_bounded() {
|
||||
let mut run = run_sample();
|
||||
run.campaigns = (0..9).map(|n| slug(&format!("c{n}"))).collect();
|
||||
assert!(matches!(PipelineDocument::Run(run).validate(), Err(PipelineRefusal::FieldBound { limit: 8, .. })));
|
||||
let mut run = run_sample();
|
||||
run.claims = (0..65).map(|n| PipelineRef { kind: PipelineKind::FollowUp, id: id("follow_up", &format!("F{n}")) }).collect();
|
||||
assert!(matches!(PipelineDocument::Run(run).validate(), Err(PipelineRefusal::FieldBound { limit: 64, .. })));
|
||||
}
|
||||
|
||||
/// every-subject-resolvable (ruling key-bound): at the widest instance and
|
||||
/// label a run's id fits a `Short`, and its resolution, and that
|
||||
/// resolution's withdrawal, key within the 111-byte resolution local.
|
||||
#[test]
|
||||
fn the_widest_run_id_fits_a_short_and_resolves_two_deep() -> Result<()> {
|
||||
let mut run = run_sample();
|
||||
run.instance = slug(&"i".repeat(64));
|
||||
run.label = l(&"a".repeat(64));
|
||||
let run_id = run_key(run)?;
|
||||
assert_eq!(run_id.len(), 64 + ":run:".len() + 64);
|
||||
assert!(run_id.len() <= 200);
|
||||
PipelineRef { kind: PipelineKind::Run, id: Short(run_id.clone()) }.validate_ref()?;
|
||||
let one = resolved_at(PipelineKind::Run, &run_id, u32::MAX)?;
|
||||
let two = resolved_at(PipelineKind::Resolution, &one, u32::MAX)?;
|
||||
assert!(two.len() <= 200);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The live registrar plus the foreign stand-in, which only the tests
|
||||
/// register.
|
||||
fn schema_cache() -> Result<CultCache> {
|
||||
|
|
@ -2859,11 +3003,11 @@ mod tests {
|
|||
};
|
||||
let document_schema = serde_json::to_value(schemars::schema_for!(PipelineDocument))?;
|
||||
let variants = document_schema["oneOf"].as_array().expect("the document schema is a oneOf");
|
||||
assert_eq!(PipelineKind::ALL.len(), 13, "thirteen kinds");
|
||||
assert_eq!(PipelineKind::ALL.len(), 14, "fourteen kinds");
|
||||
assert_eq!(
|
||||
variants.iter().map(tag).collect::<Vec<_>>(),
|
||||
PipelineKind::ALL.iter().map(|kind| kind.name().to_owned()).collect::<Vec<_>>(),
|
||||
"the document schema lists exactly the thirteen kinds, by name"
|
||||
"the document schema lists exactly the fourteen kinds, by name"
|
||||
);
|
||||
|
||||
let refusal_schema = serde_json::to_value(schemars::schema_for!(PipelineRefusal))?;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ compares file to derivation byte for byte.
|
|||
|
||||
## The pipeline catalogue
|
||||
|
||||
`index.json` is the publication manifest for every schema here: the thirteen
|
||||
`index.json` is the publication manifest for every schema here: the fourteen
|
||||
`epiphany.pipeline.*.v2` schemas, `gamecult.persona_state.v0` and the two
|
||||
`huginn.mind_*` wire schemas. The pipeline
|
||||
schemas are derived from the value types in `crates/eureka-pipeline` and checked
|
||||
|
|
|
|||
126
schemas/cultnet/epiphany.pipeline.run.v2.schema.json
Normal file
126
schemas/cultnet/epiphany.pipeline.run.v2.schema.json
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineRef": {
|
||||
"description": "A reference to another document. The id is parsed as a full pipeline id of\nthe declared `kind`, so the kind and the id's kind segment cannot disagree.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RunOperator": {
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Mind"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RunTurn": {
|
||||
"enum": [
|
||||
"PersonaTurn",
|
||||
"SelfRun"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.run.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "One launch of a Self or a Persona turn: the one record of what it took\non. `claims` are the work it holds, and a claim is live while its run\nhas no resolution in force. `budget_usd` is a canonical decimal string,\n`0` for an interactive run. Instance-rooted like stewardship: the key\nis `<instance>:run:<label>`. Whether a claim is free is admission's rule.",
|
||||
"properties": {
|
||||
"budget_usd": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"campaigns": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"claims": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"host": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"operated_by": {
|
||||
"$ref": "#/$defs/RunOperator"
|
||||
},
|
||||
"started_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"turn": {
|
||||
"$ref": "#/$defs/RunTurn"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"label",
|
||||
"operated_by",
|
||||
"turn",
|
||||
"host",
|
||||
"started_on",
|
||||
"budget_usd",
|
||||
"claims",
|
||||
"campaigns"
|
||||
],
|
||||
"title": "PipelineRun",
|
||||
"type": "object"
|
||||
}
|
||||
|
|
@ -155,6 +155,17 @@
|
|||
"title": "Epiphany Pipeline HandOff v2",
|
||||
"path": "epiphany.pipeline.hand_off.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.run.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.run.v2",
|
||||
"documentType": "epiphany.pipeline.run.v2",
|
||||
"title": "Epiphany Pipeline Run v2",
|
||||
"path": "epiphany.pipeline.run.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.mind_request.v1",
|
||||
"kind": "wire_message",
|
||||
|
|
|
|||
Loading…
Reference in a new issue