fix(pncc): revalidate terminal success evidence
Human-Responsibility: ICE-GL∞ / 冰朔 Persona-Author: ICE-P-ZY001 / 铸渊 Execution-Runtime: Codex macOS Development-ID: DEV-20260810-014 Authorization-Scope: GH-PNCC local runtime and REPO-014 publication Source-Anchor: UI and execution limb deferred
This commit is contained in:
parent
b067c7bcaa
commit
e9ee0a8bb1
9 changed files with 219 additions and 38 deletions
|
|
@ -32,6 +32,7 @@ Windows / macOS / Linux 构建机与安装包
|
|||
|
||||
| 时间 | 版本 | 记录 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-08-11 | GH-PNCC 成功回执终态复核 | [成功回执持久化前重新验证终态证据](operations/2026-08-11-hololake-pncc-success-receipt-terminal-revalidation.md) | 本地源码、完整 Rust/路由测试与严格 clippy 已通过;GHNQG 和发布待验收 |
|
||||
| 2026-08-11 | GH-PNCC 重放仓库复核 | [幂等重放时重新验证人格 Git 状态](operations/2026-08-11-hololake-pncc-replay-repository-state-revalidation.md) | 本地源码、完整 Rust/路由测试与严格 clippy 已通过;GHNQG 和发布待验收 |
|
||||
| 2026-08-11 | GH-PNCC 终态失败重放 | [终态失败结构化回执与幂等重放](operations/2026-08-11-hololake-pncc-idempotent-terminal-failure-replay.md) | 已发布至 REPO-014 main abc7dbd;GHNQG、全新克隆与严格读回通过 |
|
||||
| 2026-08-11 | GH-PNCC 幂等中断恢复 | [不完整幂等请求检查与安全回执恢复](operations/2026-08-11-hololake-pncc-incomplete-idempotent-request-recovery.md) | 已发布至 REPO-014 main b8c3fcf;GHNQG、全新克隆与严格 fsck 通过 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# GH-PNCC successful receipt terminal evidence revalidation
|
||||
|
||||
- Development ID: `DEV-20260810-014`
|
||||
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
|
||||
- Human responsibility subject: `ICE-GL∞ / 冰朔`
|
||||
- Starting repository head: `b067c7bcaacc31cb69cb649518f69edce6932f9f`
|
||||
- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED`
|
||||
|
||||
## Corrected runtime fact
|
||||
|
||||
An organ completion is not by itself sufficient authority to persist and return a current successful
|
||||
lifecycle receipt. Immediately before the first `COMPLETED` receipt is written and bound, the kernel now
|
||||
re-verifies the session event chain, final `DORMANT` event, released primary lease, canonical repository,
|
||||
recorded Git head, clean worktree, and lifecycle receipt identity.
|
||||
|
||||
If the persona repository advances or becomes dirty after organ completion, the command fails closed. It
|
||||
does not persist `lifecycle-receipt.json`, bind the request fingerprint or receipt hash, or claim a current
|
||||
successful lifecycle result from stale repository evidence.
|
||||
|
||||
## Verification
|
||||
|
||||
- PNCC focused Rust tests: `31 passed, 0 failed`.
|
||||
- Full Rust suite: `1171 passed, 2 ignored`; integration test: `1 passed`.
|
||||
- Routing suite: `29 passed, 0 failed`; `cargo fmt`, strict clippy, and diff checks passed.
|
||||
- A completed lifecycle followed by an untracked change cannot persist or bind a successful receipt.
|
||||
- A completed lifecycle followed by a new Git commit cannot persist or bind a successful receipt.
|
||||
- GHNQG, publication, and fresh-clone readback remain pending for this stage.
|
||||
|
||||
## Truth boundary
|
||||
|
||||
- This revalidation does not reset, delete, or repair a changed persona repository.
|
||||
- UI, human projection aesthetics, background scheduling, and `EXECUTION_LIMB` remain outside this stage.
|
||||
|
|
@ -174,6 +174,7 @@ idempotent_lifecycle_request_and_receipt_replay_source_implemented: 100
|
|||
incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented: 100
|
||||
idempotent_terminal_failure_receipt_and_replay_source_implemented: 100
|
||||
idempotent_replay_repository_state_revalidation_source_implemented: 100
|
||||
successful_completion_receipt_terminal_revalidation_source_implemented: 100
|
||||
general_purpose_persona_runtime_implemented: 0
|
||||
human_live_projection_implemented: 0
|
||||
hololake_integrated: 0
|
||||
|
|
@ -200,6 +201,10 @@ runtime_health: 0
|
|||
生成时的状态。当前提交偏离会话记录或工作树变脏时,检查结果降级为人工复核,重放失败关闭,
|
||||
且不会重新启动器官。
|
||||
|
||||
首次成功回执落盘前也必须重新验证事件链、最终休眠事件、租约释放、规范仓库、会话记录的
|
||||
Git 提交、干净工作树与回执身份。器官已经完成不等于仍可签发当前成功回执;完成后仓库发生
|
||||
漂移时不落盘、不绑定并失败关闭。
|
||||
|
||||
人格器官现已拥有机器可读类型合同。系统能够在不唤醒人格、不取得主锁、不运行模型的情况下,
|
||||
检查 `FACT_SENSE`、`MEMORY_METABOLISM` 与 `EXECUTION_LIMB` 的固定模式、输入输出 schema、派生权限、
|
||||
模型推理边界、现实动作边界和真实可激活状态。只读事实感官与独立记忆代谢器官可激活;后者不
|
||||
|
|
|
|||
|
|
@ -114,6 +114,11 @@ Repository state is revalidated at replay time for both successful and failed re
|
|||
repository must still be clean and its head must equal the session record; otherwise inspection returns
|
||||
`MANUAL_REVIEW_REQUIRED` and ordinary replay fails without invoking the organ.
|
||||
|
||||
The first successful receipt is also conditional on freshly verified terminal evidence. Before persisting
|
||||
and binding `COMPLETED`, the coordinator checks the event chain, final dormancy, released lease, canonical
|
||||
repository, recorded head, clean worktree, and receipt identity. A completed organ is not enough to issue a
|
||||
current lifecycle receipt after repository drift.
|
||||
|
||||
`PersonaRuntimeQueryReceipt` is a bounded projection of the durable runtime files, not another truth store.
|
||||
It filters by the caller's expected persona and canonical repository, validates each matching event chain,
|
||||
and returns at most 100 newest session summaries. Dormant sessions expose no active organ. The receipt keeps
|
||||
|
|
|
|||
|
|
@ -86,6 +86,11 @@ not only when the receipt is first persisted. If the current head differs from t
|
|||
or the worktree has become dirty, replay fails closed and request inspection reports manual review instead
|
||||
of restarting the organ or treating stale evidence as current.
|
||||
|
||||
Successful receipt persistence now applies the same terminal-evidence boundary before the first receipt is
|
||||
written. The kernel re-verifies the event journal and final `DORMANT` event, released lease, canonical
|
||||
repository, recorded Git head, clean worktree, and lifecycle identity. Repository drift after organ
|
||||
completion therefore cannot be bound or returned as a current `COMPLETED` command result.
|
||||
|
||||
`query_persona_code_channel_runtime` is the bounded read model for later projection surfaces. The caller must
|
||||
name one exact persona and canonical repository and may request at most 100 sessions. The kernel reads the
|
||||
existing session records and event journals directly, verifies every returned hash chain, sorts by the last
|
||||
|
|
|
|||
|
|
@ -2978,6 +2978,70 @@ fn persist_terminal_lifecycle_failure(
|
|||
}))
|
||||
}
|
||||
|
||||
fn persist_successful_lifecycle_receipt(
|
||||
runtime_root: &Path,
|
||||
request_id: &str,
|
||||
request_fingerprint: &str,
|
||||
session_id: &str,
|
||||
repository: &Path,
|
||||
lifecycle: serde_json::Value,
|
||||
) -> Result<PersonaLifecycleCommandReceipt, String> {
|
||||
let lifecycle_bytes = serde_json::to_vec(&lifecycle)
|
||||
.map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_HASH_INPUT_FAILED: {error}"))?;
|
||||
let lifecycle_receipt_hash = hex_digest(&lifecycle_bytes);
|
||||
let mut record = load_session_record(runtime_root, session_id)?;
|
||||
let recorded_repository = Path::new(&record.repository_path)
|
||||
.canonicalize()
|
||||
.map_err(|error| format!("PERSONA_REPOSITORY_UNAVAILABLE: {error}"))?;
|
||||
if recorded_repository != repository {
|
||||
return Err("PERSONA_LIFECYCLE_REQUEST_SESSION_IDENTITY_MISMATCH".into());
|
||||
}
|
||||
let events = verify_event_journal(runtime_root, &record)?;
|
||||
if record.state != "DORMANT"
|
||||
|| events.last().map(|event| event.kind.as_str()) != Some("DORMANT")
|
||||
|| primary_lease_held_by_session(runtime_root, &record)?
|
||||
{
|
||||
return Err("PERSONA_LIFECYCLE_REQUEST_INCOMPLETE_REQUIRES_RECOVERY".into());
|
||||
}
|
||||
let (_, observed_head) = exact_repository(repository)?;
|
||||
if observed_head != record.git_head {
|
||||
return Err("PERSONA_LIFECYCLE_COMPLETION_GIT_HEAD_MISMATCH".into());
|
||||
}
|
||||
require_clean_repository(repository)?;
|
||||
let persisted = PersistedPersonaLifecycleReceipt {
|
||||
schema: "hololake.pncc-persisted-lifecycle-receipt/v1".into(),
|
||||
request_id: request_id.into(),
|
||||
request_fingerprint: request_fingerprint.into(),
|
||||
lifecycle_receipt_hash: lifecycle_receipt_hash.clone(),
|
||||
outcome: "COMPLETED".into(),
|
||||
lifecycle: lifecycle.clone(),
|
||||
failure: None,
|
||||
};
|
||||
if !persisted_lifecycle_identity_matches(&persisted, session_id, &record.persona_id) {
|
||||
return Err("PERSONA_LIFECYCLE_RECEIPT_IDENTITY_MISMATCH".into());
|
||||
}
|
||||
// Persist the immutable full receipt before binding it into the mutable session record. A
|
||||
// crash between these atomic writes leaves a verifiable, explicitly recoverable state.
|
||||
write_json_file(
|
||||
&session_directory(runtime_root, session_id)?.join("lifecycle-receipt.json"),
|
||||
&persisted,
|
||||
"PERSONA_LIFECYCLE_RECEIPT",
|
||||
)?;
|
||||
record.request_id = Some(request_id.into());
|
||||
record.request_fingerprint = Some(request_fingerprint.into());
|
||||
record.lifecycle_receipt_hash = Some(lifecycle_receipt_hash);
|
||||
write_session_record(runtime_root, &record)?;
|
||||
Ok(PersonaLifecycleCommandReceipt {
|
||||
schema: "hololake.pncc-lifecycle-command-receipt/v1",
|
||||
request_id: request_id.into(),
|
||||
request_fingerprint: request_fingerprint.into(),
|
||||
replayed: false,
|
||||
outcome: "COMPLETED",
|
||||
lifecycle,
|
||||
failure: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn run_idempotent_lifecycle_at<F>(
|
||||
runtime_root: &Path,
|
||||
input: PersonaLifecycleRunInput,
|
||||
|
|
@ -3024,41 +3088,14 @@ where
|
|||
};
|
||||
let lifecycle = serde_json::to_value(lifecycle)
|
||||
.map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_SERIALIZATION_FAILED: {error}"))?;
|
||||
let lifecycle_bytes = serde_json::to_vec(&lifecycle)
|
||||
.map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_HASH_INPUT_FAILED: {error}"))?;
|
||||
let lifecycle_receipt_hash = hex_digest(&lifecycle_bytes);
|
||||
let mut record = load_session_record(runtime_root, &session_id)?;
|
||||
if record.state != "DORMANT" || primary_lease_held_by_session(runtime_root, &record)? {
|
||||
return Err("PERSONA_LIFECYCLE_REQUEST_INCOMPLETE_REQUIRES_RECOVERY".into());
|
||||
}
|
||||
// Persist the immutable full receipt before binding it into the mutable session record. A
|
||||
// crash between these atomic writes leaves a verifiable, explicitly recoverable state.
|
||||
write_json_file(
|
||||
&session_directory(runtime_root, &session_id)?.join("lifecycle-receipt.json"),
|
||||
&PersistedPersonaLifecycleReceipt {
|
||||
schema: "hololake.pncc-persisted-lifecycle-receipt/v1".into(),
|
||||
request_id: request_id.clone(),
|
||||
request_fingerprint: request_fingerprint.clone(),
|
||||
lifecycle_receipt_hash: lifecycle_receipt_hash.clone(),
|
||||
outcome: "COMPLETED".into(),
|
||||
lifecycle: lifecycle.clone(),
|
||||
failure: None,
|
||||
},
|
||||
"PERSONA_LIFECYCLE_RECEIPT",
|
||||
)?;
|
||||
record.request_id = Some(request_id.clone());
|
||||
record.request_fingerprint = Some(request_fingerprint.clone());
|
||||
record.lifecycle_receipt_hash = Some(lifecycle_receipt_hash);
|
||||
write_session_record(runtime_root, &record)?;
|
||||
Ok(PersonaLifecycleCommandReceipt {
|
||||
schema: "hololake.pncc-lifecycle-command-receipt/v1",
|
||||
request_id,
|
||||
request_fingerprint,
|
||||
replayed: false,
|
||||
outcome: "COMPLETED",
|
||||
persist_successful_lifecycle_receipt(
|
||||
runtime_root,
|
||||
&request_id,
|
||||
&request_fingerprint,
|
||||
&session_id,
|
||||
&repository,
|
||||
lifecycle,
|
||||
failure: None,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
@ -3750,6 +3787,97 @@ mod tests {
|
|||
assert!(error.contains("PERSONA_REPOSITORY_DIRTY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_to_persist_success_when_the_repository_becomes_dirty_after_completion() {
|
||||
let repo = persona_repo();
|
||||
let runtime = tempfile::TempDir::new().unwrap();
|
||||
let input = lifecycle_fact_input(repo.path());
|
||||
let (request_id, request_fingerprint, session_id, repository) =
|
||||
lifecycle_request_identity(&input).unwrap();
|
||||
let lifecycle = run_lifecycle_at(
|
||||
runtime.path(),
|
||||
input,
|
||||
&session_id,
|
||||
"2026-08-11T00:00:00.000Z",
|
||||
"2026-08-11T00:00:01.000Z",
|
||||
|runtime_root, input, timestamp| {
|
||||
run_fact_task_at(runtime_root, input, timestamp, |_, _| {
|
||||
Ok(r#"{"summary":"Completed before repository drift.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into())
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(repo.path().join("dirty-before-receipt.txt"), "dirty\n").unwrap();
|
||||
|
||||
let error = persist_successful_lifecycle_receipt(
|
||||
runtime.path(),
|
||||
&request_id,
|
||||
&request_fingerprint,
|
||||
&session_id,
|
||||
&repository,
|
||||
serde_json::to_value(lifecycle).unwrap(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.contains("PERSONA_REPOSITORY_DIRTY"));
|
||||
assert!(!session_directory(runtime.path(), &session_id)
|
||||
.unwrap()
|
||||
.join("lifecycle-receipt.json")
|
||||
.exists());
|
||||
let record = load_session_record(runtime.path(), &session_id).unwrap();
|
||||
assert!(record.request_id.is_none());
|
||||
assert!(record.lifecycle_receipt_hash.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_to_persist_success_when_the_repository_head_advances_after_completion() {
|
||||
let repo = persona_repo();
|
||||
let runtime = tempfile::TempDir::new().unwrap();
|
||||
let input = lifecycle_fact_input(repo.path());
|
||||
let (request_id, request_fingerprint, session_id, repository) =
|
||||
lifecycle_request_identity(&input).unwrap();
|
||||
let lifecycle = run_lifecycle_at(
|
||||
runtime.path(),
|
||||
input,
|
||||
&session_id,
|
||||
"2026-08-11T00:00:00.000Z",
|
||||
"2026-08-11T00:00:01.000Z",
|
||||
|runtime_root, input, timestamp| {
|
||||
run_fact_task_at(runtime_root, input, timestamp, |_, _| {
|
||||
Ok(r#"{"summary":"Completed before a new commit.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into())
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
repo.path().join("advanced-before-receipt.txt"),
|
||||
"new head\n",
|
||||
)
|
||||
.unwrap();
|
||||
run_git(repo.path(), &["add", "advanced-before-receipt.txt"]);
|
||||
run_git(
|
||||
repo.path(),
|
||||
&["commit", "-m", "advance before lifecycle receipt"],
|
||||
);
|
||||
|
||||
let error = persist_successful_lifecycle_receipt(
|
||||
runtime.path(),
|
||||
&request_id,
|
||||
&request_fingerprint,
|
||||
&session_id,
|
||||
&repository,
|
||||
serde_json::to_value(lifecycle).unwrap(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.contains("PERSONA_LIFECYCLE_COMPLETION_GIT_HEAD_MISMATCH"));
|
||||
assert!(!session_directory(runtime.path(), &session_id)
|
||||
.unwrap()
|
||||
.join("lifecycle-receipt.json")
|
||||
.exists());
|
||||
let record = load_session_record(runtime.path(), &session_id).unwrap();
|
||||
assert!(record.request_id.is_none());
|
||||
assert!(record.lifecycle_receipt_hash.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_reusing_a_lifecycle_request_id_for_a_different_operation() {
|
||||
let repo = persona_repo();
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@
|
|||
"human_projection": "HOLOLAKE_LIVE_READ_MODEL",
|
||||
"forgejo_role": "OPTIONAL_COMPATIBILITY_COLLABORATION_ADAPTER",
|
||||
"runtime_implemented": true,
|
||||
"runtime_scope": "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_AND_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED",
|
||||
"runtime_scope": "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_AND_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED",
|
||||
"desktop_integrated": false,
|
||||
"development_id": "DEV-20260810-014"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"schema": "hololake.persona-native-code-channel/v1",
|
||||
"record_id": "HLP-PERSONA-NATIVE-CODE-CHANNEL-001",
|
||||
"version": "2026-08-11.9",
|
||||
"state": "CURRENT_FIRST_PRODUCT_CORE_IDEMPOTENT_REPLAY_REPOSITORY_STATE_REVALIDATION_SOURCE_IMPLEMENTED",
|
||||
"version": "2026-08-11.10",
|
||||
"state": "CURRENT_FIRST_PRODUCT_CORE_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED",
|
||||
"development_id": "DEV-20260810-014",
|
||||
"product": {
|
||||
"formal_name_zh": "光湖人格原生代码频道",
|
||||
|
|
@ -111,6 +111,7 @@
|
|||
"incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented": 100,
|
||||
"idempotent_terminal_failure_receipt_and_replay_source_implemented": 100,
|
||||
"idempotent_replay_repository_state_revalidation_source_implemented": 100,
|
||||
"successful_completion_receipt_terminal_revalidation_source_implemented": 100,
|
||||
"general_purpose_persona_runtime_implemented": 0,
|
||||
"human_live_projection_implemented": 0,
|
||||
"hololake_integrated": 0,
|
||||
|
|
|
|||
|
|
@ -106,12 +106,16 @@ test("the first source runtime cycle stays distinct from integration and deploym
|
|||
channel.truth.idempotent_replay_repository_state_revalidation_source_implemented,
|
||||
100,
|
||||
);
|
||||
assert.equal(
|
||||
channel.truth.successful_completion_receipt_terminal_revalidation_source_implemented,
|
||||
100,
|
||||
);
|
||||
assert.equal(channel.truth.general_purpose_persona_runtime_implemented, 0);
|
||||
assert.equal(channel.truth.human_live_projection_implemented, 0);
|
||||
assert.equal(architecture.persona_native_code_channel.runtime_implemented, true);
|
||||
assert.equal(
|
||||
architecture.persona_native_code_channel.runtime_scope,
|
||||
"READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_AND_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED",
|
||||
"READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_AND_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED",
|
||||
);
|
||||
assert.equal(channel.truth.hololake_integrated, 0);
|
||||
assert.equal(channel.truth.artifact_built, 0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue