feat(triggers): show a materialized column in pic triggers ls --app (D1)
A materialized copy of an M5 group stateful template (cron/queue/email) now reads as `materialized = true` — inherited, read-only — distinct from a hand-authored trigger. Threads a derived `materialized` bool (= `materialized_from IS NOT NULL`) row → domain → API → CLI, mirroring the `sealed`/`shared` columns; `list_for_app` SELECTs `materialized_from`. No migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1760,6 +1760,11 @@ pub struct TriggerDto {
|
||||
pub enabled: bool,
|
||||
pub dispatch_mode: String,
|
||||
pub retry_max_attempts: u32,
|
||||
/// §4.5 M5: true for a materialized copy of a group stateful template
|
||||
/// (inherited, read-only). `default` so an older server without the field
|
||||
/// still deserializes.
|
||||
#[serde(default)]
|
||||
pub materialized: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub details: Value,
|
||||
|
||||
@@ -23,6 +23,7 @@ pub async fn ls(app: &str, mode: OutputMode) -> Result<()> {
|
||||
"enabled",
|
||||
"dispatch",
|
||||
"retry_max",
|
||||
"materialized",
|
||||
"created_at",
|
||||
]);
|
||||
for t in resp.triggers {
|
||||
@@ -33,6 +34,7 @@ pub async fn ls(app: &str, mode: OutputMode) -> Result<()> {
|
||||
t.enabled.to_string(),
|
||||
t.dispatch_mode,
|
||||
t.retry_max_attempts.to_string(),
|
||||
t.materialized.to_string(),
|
||||
t.created_at.to_rfc3339(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,22 @@ fn app_cron_count(env: &common::TestEnv, app: &str) -> usize {
|
||||
app_trigger_count(env, app, "cron")
|
||||
}
|
||||
|
||||
/// The `materialized` cell (index 6) of the first row of `kind` in
|
||||
/// `pic triggers ls --app`. Columns: id kind script_id enabled dispatch
|
||||
/// retry_max materialized created_at.
|
||||
fn app_trigger_materialized(env: &common::TestEnv, app: &str, kind: &str) -> Option<String> {
|
||||
let out = common::pic_as(env)
|
||||
.args(["triggers", "ls", "--app", app])
|
||||
.output()
|
||||
.expect("triggers ls");
|
||||
String::from_utf8(out.stdout)
|
||||
.unwrap()
|
||||
.lines()
|
||||
.map(common::cells)
|
||||
.find(|c| c.get(1) == Some(&kind))
|
||||
.and_then(|c| c.get(6).map(|s| (*s).to_string()))
|
||||
}
|
||||
|
||||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||||
#[test]
|
||||
fn group_cron_template_materializes_for_descendant_apps() {
|
||||
@@ -110,6 +126,13 @@ fn group_cron_template_materializes_for_descendant_apps() {
|
||||
1,
|
||||
"a descendant app must have a materialized cron trigger"
|
||||
);
|
||||
// D1: the materialized copy reads `materialized = true` (inherited,
|
||||
// read-only) — distinct from a hand-authored trigger.
|
||||
assert_eq!(
|
||||
app_trigger_materialized(&env, &app, "cron").as_deref(),
|
||||
Some("true"),
|
||||
"a materialized cron copy must show materialized = true"
|
||||
);
|
||||
|
||||
// Re-apply of the group is a NoOp for materialization (no duplicate copy).
|
||||
common::pic_as(&env)
|
||||
|
||||
@@ -57,6 +57,7 @@ fn create_kv_and_show_in_ls() {
|
||||
"enabled",
|
||||
"dispatch",
|
||||
"retry_max",
|
||||
"materialized",
|
||||
"created_at"
|
||||
]
|
||||
);
|
||||
@@ -67,6 +68,11 @@ fn create_kv_and_show_in_ls() {
|
||||
.find(|c| c.get(1).copied() == Some("kv"))
|
||||
.unwrap_or_else(|| panic!("kv trigger missing from ls: {stdout}"));
|
||||
assert_eq!(row[2], script_id);
|
||||
// D1: a hand-authored trigger is not materialized.
|
||||
assert_eq!(
|
||||
row[6], "false",
|
||||
"an app-authored trigger shows materialized = false"
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||||
|
||||
Reference in New Issue
Block a user