Implement HARP and CPL milestones through v0.5.11
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const source = (path) => readFile(new URL(path, import.meta.url), "utf8");
|
||||
|
||||
test("routes both manuscript surfaces through instrumented TipTap transactions", async () => {
|
||||
const frontend = await source("../src/Thinkloom.tsx");
|
||||
|
||||
assert.match(frontend, /useEditor\(\{[\s\S]*onUpdate:/);
|
||||
assert.match(frontend, /apply_composition_command/);
|
||||
assert.match(frontend, /label="Final manuscript editor"/);
|
||||
assert.doesNotMatch(frontend, /finalEditor|<textarea[^>]+aria-label="Final manuscript"/);
|
||||
assert.match(frontend, /onPasteCapture[\s\S]*origin: "imported_or_pasted"/);
|
||||
assert.doesNotMatch(frontend, /onPasteCapture[\s\S]{0,180}recorded_direct_human_input/);
|
||||
});
|
||||
|
||||
test("captures every required coalescing boundary without originality heuristics", async () => {
|
||||
const [frontend, composition] = await Promise.all([
|
||||
source("../src/Thinkloom.tsx"),
|
||||
source("../src-tauri/src/provenance/composition.rs"),
|
||||
]);
|
||||
|
||||
for (const boundary of [
|
||||
"idle", "focus_loss", "section_change", "ai_operation", "checkpoint",
|
||||
"phase_change", "explicit_save", "document_close",
|
||||
]) assert.match(`${frontend}\n${composition}`, new RegExp(`\\b${boundary}\\b`));
|
||||
|
||||
for (const origin of [
|
||||
"recorded_direct_human_input", "human_expressive_input_via_transcription",
|
||||
"accepted_ai_output", "imported_or_pasted", "system_restoration", "unattested",
|
||||
]) assert.match(`${frontend}\n${composition}`, new RegExp(origin));
|
||||
|
||||
assert.doesNotMatch(composition, /edit[_ -]?count|elapsed[_ -]?time|word[_ -]?count|retained[_ -]?word[_ -]?ratio/i);
|
||||
});
|
||||
|
||||
test("binds partial AI dispositions and deterministically replays surviving-span lineage", async () => {
|
||||
const [frontend, composition, native, writer] = await Promise.all([
|
||||
source("../src/Thinkloom.tsx"),
|
||||
source("../src-tauri/src/provenance/composition.rs"),
|
||||
source("../src-tauri/src/lib.rs"),
|
||||
source("../src-tauri/src/provenance/writer.rs"),
|
||||
]);
|
||||
|
||||
assert.match(frontend, /invocationId: project\.generation\.id/);
|
||||
assert.match(frontend, /acceptedRanges: \[\{ start: 0, end: acceptedEnd \}\]/);
|
||||
assert.match(frontend, /rejectedRanges: acceptedEnd < responseEnd/);
|
||||
assert.match(composition, /ai-acceptance-disposition/);
|
||||
assert.match(composition, /result_revision_id/);
|
||||
assert.match(composition, /record_type == "composition-command"/);
|
||||
assert.match(composition, /lineage_reference_ids/);
|
||||
assert.match(composition, /RecordedOrigin::Unattested/);
|
||||
assert.match(writer, /rebuild_projection_cache/);
|
||||
assert.match(native, /manuscript\/manuscript\.md/);
|
||||
assert.match(composition, /replays_manual_paste_ai_revision_and_restoration_with_lineage/);
|
||||
assert.match(composition, /unicode_scalar_diff_and_partial_ai_ranges_are_exact/);
|
||||
assert.match(composition, /refuses_a_stale_preimage_without_committing_an_event/);
|
||||
assert.match(composition, /concurrent_edits_against_one_preimage_commit_exactly_once/);
|
||||
assert.match(composition, /retries_are_idempotent_and_action_id_reuse_conflicts/);
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const source = (path) => readFile(new URL(path, import.meta.url), "utf8");
|
||||
|
||||
test("freezes contribution maps from native composition revisions and exact deposits", async () => {
|
||||
const [native, map, modules] = await Promise.all([
|
||||
source("../src-tauri/src/lib.rs"),
|
||||
source("../src-tauri/src/provenance/contribution_map.rs"),
|
||||
source("../src-tauri/src/provenance/mod.rs"),
|
||||
]);
|
||||
|
||||
assert.match(modules, /pub mod contribution_map;/);
|
||||
assert.match(native, /fn freeze_contribution_map/);
|
||||
assert.match(native, /fn load_contribution_map/);
|
||||
assert.match(native, /contribution_map::freeze_current/);
|
||||
assert.match(native, /"deposits"/);
|
||||
assert.match(map, /record_type: "deposit-snapshot"/);
|
||||
assert.match(map, /record_type: "contribution-map"/);
|
||||
assert.match(map, /record_type: "contribution-map-bundle"/);
|
||||
assert.match(map, /deposit_sha256/);
|
||||
assert.match(map, /manuscript_revision_id/);
|
||||
});
|
||||
|
||||
test("provides complete scalar coverage with deterministic structural locators and ancestry", async () => {
|
||||
const map = await source("../src-tauri/src/provenance/contribution_map.rs");
|
||||
|
||||
assert.match(map, /coordinate_system: "unicode_scalar"/);
|
||||
assert.match(map, /validate_source_coverage/);
|
||||
assert.match(map, /validate_map_coverage/);
|
||||
assert.match(map, /merge_source_spans/);
|
||||
assert.match(map, /ancestry_segment_id\.clone\(\)/);
|
||||
assert.match(map, /chapter: Some/);
|
||||
assert.match(map, /paragraph: Some/);
|
||||
assert.match(map, /page: Some/);
|
||||
assert.doesNotMatch(map, /locale|Collator|toLocaleString/);
|
||||
assert.match(map, /canonical_map_bytes/);
|
||||
assert.match(map, /identical_canonical_input_is_byte_identical_for_any_span_order/);
|
||||
assert.match(map, /equivalent_adjacent_source_splits_merge_to_the_same_map/);
|
||||
});
|
||||
|
||||
test("separates assertions from origin and exposes every evidence boundary", async () => {
|
||||
const [map, schema] = await Promise.all([
|
||||
source("../src-tauri/src/provenance/contribution_map.rs"),
|
||||
source("../schemas/provenance/v1/contribution-map.schema.json"),
|
||||
]);
|
||||
|
||||
for (const predicate of ["included_in_deposit", "selected_by_human", "arranged_by_human"])
|
||||
assert.match(map, new RegExp(predicate));
|
||||
assert.match(map, /assertion_evaluations/);
|
||||
for (const status of ["stale", "degraded", "unverified", "unattested"])
|
||||
assert.match(map, new RegExp(`"${status}"`));
|
||||
assert.match(map, /not a human-authorship percentage/);
|
||||
assert.match(schema, /denominator_unit/);
|
||||
assert.match(schema, /denominator_definition/);
|
||||
assert.match(map, /frozen_map_becomes_stale_after_a_later_composition_revision/);
|
||||
assert.match(map, /verified_frozen_map_is_exact_and_reused_for_identical_input/);
|
||||
assert.match(map, /missing_frozen_deposit_is_visibly_degraded/);
|
||||
assert.match(map, /inconclusive_source_verification_is_visibly_unverified/);
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const source = (path) => readFile(new URL(path, import.meta.url), "utf8");
|
||||
|
||||
test("creates the six milestone 10 artifacts through the native HARP export service", async () => {
|
||||
const [native, exporter, modules, ui] = await Promise.all([
|
||||
source("../src-tauri/src/lib.rs"),
|
||||
source("../src-tauri/src/provenance/export.rs"),
|
||||
source("../src-tauri/src/provenance/mod.rs"),
|
||||
source("../src/HarpExportPanel.tsx"),
|
||||
]);
|
||||
|
||||
assert.match(modules, /pub mod export;/);
|
||||
assert.match(native, /fn export_harp_artifacts/);
|
||||
assert.match(native, /fn verify_harp_sanitized_archive/);
|
||||
assert.match(ui, /export_harp_artifacts/);
|
||||
assert.match(ui, /verify_harp_sanitized_archive/);
|
||||
|
||||
for (const role of [
|
||||
"registration_worksheet",
|
||||
"human_readable_harp",
|
||||
"machine_readable_harp",
|
||||
"deposit_copy",
|
||||
"sanitized_supporting_archive",
|
||||
"full_private_archive",
|
||||
]) assert.match(exporter, new RegExp(`"${role}"`));
|
||||
});
|
||||
|
||||
test("discloses and hash-binds every required sanitized omission category", async () => {
|
||||
const [exporter, generator] = await Promise.all([
|
||||
source("../src-tauri/src/provenance/export.rs"),
|
||||
source("../scripts/generate-provenance-stage2.mjs"),
|
||||
]);
|
||||
|
||||
for (const category of [
|
||||
"private_conversation",
|
||||
"rejected_model_output",
|
||||
"credential_authorization_material",
|
||||
"personal_identifier",
|
||||
"internal_path",
|
||||
"provider_metadata_not_required",
|
||||
"protected_source_body",
|
||||
]) {
|
||||
assert.match(exporter, new RegExp(`"${category}"`));
|
||||
assert.match(generator, new RegExp(`"${category}"`));
|
||||
}
|
||||
assert.match(exporter, /retained_binding_sha256/);
|
||||
assert.match(exporter, /disclosure_sha256/);
|
||||
assert.match(exporter, /rules_sha256/);
|
||||
assert.match(exporter, /canonical_digest\(&identity\)/);
|
||||
});
|
||||
|
||||
test("verifies retained files without claiming sanitized completeness", async () => {
|
||||
const [exporter, ui] = await Promise.all([
|
||||
source("../src-tauri/src/provenance/export.rs"),
|
||||
source("../src/HarpExportPanel.tsx"),
|
||||
]);
|
||||
|
||||
assert.match(exporter, /selective_disclosed_subset/);
|
||||
assert.match(exporter, /verified_selective/);
|
||||
assert.match(exporter, /intentionally incomplete/);
|
||||
assert.match(exporter, /sha256_digest\(&bytes\) != binding\["sha256"\]/);
|
||||
assert.match(exporter, /export must not mutate the CPL chain/);
|
||||
assert.doesNotMatch(exporter, /CplService::new/);
|
||||
assert.match(ui, /selective evidence subset/i);
|
||||
assert.match(ui, /does not claim the omitted private history is present/i);
|
||||
assert.match(ui, /Full private archive/);
|
||||
assert.match(ui, /Redact the declared author name/);
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const source = (path) => readFile(new URL(path, import.meta.url), "utf8");
|
||||
|
||||
test("generates approved deterministic HARP artifacts through the native CPL service", async () => {
|
||||
const [native, harp, modules] = await Promise.all([
|
||||
source("../src-tauri/src/lib.rs"),
|
||||
source("../src-tauri/src/provenance/harp.rs"),
|
||||
source("../src-tauri/src/provenance/mod.rs"),
|
||||
]);
|
||||
|
||||
assert.match(modules, /pub mod harp;/);
|
||||
assert.match(native, /fn generate_harp/);
|
||||
assert.match(native, /fn load_harp/);
|
||||
assert.match(native, /harp::generate_current/);
|
||||
assert.match(harp, /HARP_GENERATION_APPROVED/);
|
||||
assert.match(harp, /HARP_GENERATED/);
|
||||
assert.match(harp, /human-authorship-record/);
|
||||
assert.match(harp, /harp-export-manifest/);
|
||||
assert.match(harp, /harp-generation-bundle/);
|
||||
});
|
||||
|
||||
test("emits every milestone 8 report with common binding metadata", async () => {
|
||||
const harp = await source("../src-tauri/src/provenance/harp.rs");
|
||||
|
||||
for (const artifact of [
|
||||
"human-authorship-summary.md",
|
||||
"final-text-contribution-map.svg",
|
||||
"representative-transformations.md",
|
||||
"ai-system-disclosure.md",
|
||||
"coverage-and-limitations.md",
|
||||
"registration-language.md",
|
||||
"harp.json",
|
||||
"verification-report.json",
|
||||
"supporting-archive-manifest.json",
|
||||
]) assert.match(harp, new RegExp(artifact.replaceAll(".", "\\.")));
|
||||
|
||||
for (const binding of [
|
||||
"deposit_sha256",
|
||||
"manuscript_revision_id",
|
||||
"cpl_chain_head",
|
||||
"cpl_event_sequence",
|
||||
"harp_schema_version",
|
||||
"harp_generator_version",
|
||||
"application_version",
|
||||
"policy_profile_version",
|
||||
"policy_retrieval_date",
|
||||
"sanitization_profile",
|
||||
"legal_scope_statement",
|
||||
]) assert.match(harp, new RegExp(binding));
|
||||
});
|
||||
|
||||
test("does not use an LLM, a legal classifier, or a human percentage", async () => {
|
||||
const harp = await source("../src-tauri/src/provenance/harp.rs");
|
||||
|
||||
assert.doesNotMatch(harp, /generate_text\(/);
|
||||
assert.doesNotMatch(harp, /reqwest/);
|
||||
assert.doesNotMatch(harp, /human_percentage/);
|
||||
assert.match(harp, /not a human-authorship percentage/);
|
||||
assert.match(harp, /Copyright Office/);
|
||||
});
|
||||
|
||||
test("binds staleness to manuscript, deposit, policy, assertion, and dependency changes", async () => {
|
||||
const harp = await source("../src-tauri/src/provenance/harp.rs");
|
||||
|
||||
for (const reason of [
|
||||
"manuscript_revision_changed",
|
||||
"deposit_digest_changed",
|
||||
"policy_profile_changed",
|
||||
"assertion_set_changed",
|
||||
"dependency_set_changed",
|
||||
]) assert.match(harp, new RegExp(reason));
|
||||
assert.match(harp, /fn dependency_changes_are_stale/);
|
||||
assert.match(harp, /applicability_status.*stale/s);
|
||||
});
|
||||
@@ -20,16 +20,18 @@ test("defines a native-only Thinkloom application shell", async () => {
|
||||
});
|
||||
|
||||
test("implements the control and privacy contracts", async () => {
|
||||
const [source, css] = await Promise.all([
|
||||
const [source, provenance, css] = await Promise.all([
|
||||
readFile(new URL("../src/Thinkloom.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src/ProvenanceWorkspace.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src/globals.css", import.meta.url), "utf8"),
|
||||
]);
|
||||
|
||||
for (const phrase of ["Append to ideas", "Summarize draft", "Lore & context", "Insert at cursor", "Replace selection", "New section", "Discard", "History recorded", "No audio retained", "Approve for this project", "Relationships, not percentages"]) {
|
||||
for (const phrase of ["Append to ideas", "Summarize draft", "Lore & context", "Insert at cursor", "Replace selection", "New section", "Discard", "History recorded", "No audio retained", "Approve for this project"]) {
|
||||
assert.match(source, new RegExp(phrase, "i"));
|
||||
}
|
||||
assert.match(provenance, /Provenance coverage is not a human-authorship score/);
|
||||
assert.match(source, /GENERATION_PARTIALLY_ACCEPTED/);
|
||||
assert.match(source, /CLOUD_PROCESSING_APPROVED/);
|
||||
assert.match(source, /CLOUD_APPROVAL_CHANGED/);
|
||||
assert.match(source, /store_provider_secret/);
|
||||
assert.match(source, /New empty project/);
|
||||
assert.match(source, /function createEmptyProject/);
|
||||
@@ -139,7 +141,7 @@ test("externalizes and documents every model prompt", async () => {
|
||||
}
|
||||
|
||||
const version = JSON.parse(packageRaw).version;
|
||||
assert.equal(version, "0.5.0");
|
||||
assert.equal(version, "0.5.11");
|
||||
const packageLock = JSON.parse(packageLockRaw);
|
||||
assert.equal(packageLock.version, version);
|
||||
assert.equal(packageLock.packages[""].version, version);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const rustModules = [
|
||||
"canonical",
|
||||
"identifiers",
|
||||
"records",
|
||||
"writer",
|
||||
"ledger",
|
||||
"recovery",
|
||||
"verifier",
|
||||
"composition",
|
||||
"assertions",
|
||||
"projections",
|
||||
"harp",
|
||||
"export",
|
||||
];
|
||||
|
||||
test("places all authoritative provenance operations behind the native CPL service", async () => {
|
||||
const [frontend, lib, moduleSources] = await Promise.all([
|
||||
readFile(new URL("../src/Thinkloom.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src-tauri/src/lib.rs", import.meta.url), "utf8"),
|
||||
Promise.all(rustModules.map((name) => readFile(new URL(`../src-tauri/src/provenance/${name}.rs`, import.meta.url), "utf8"))),
|
||||
]);
|
||||
const native = moduleSources.join("\n");
|
||||
|
||||
assert.match(lib, /pub mod provenance/);
|
||||
assert.match(frontend, /apply_phase1_command/);
|
||||
assert.doesNotMatch(frontend, /persistNativeState|persist_state/);
|
||||
assert.match(frontend, /verify_provenance/);
|
||||
assert.match(frontend, /Native integrity verification/);
|
||||
assert.doesNotMatch(frontend, /previousHash|manuscriptHash|const hash\s*=|event\.hash/);
|
||||
assert.doesNotMatch(frontend, /History verified:.*linked events/);
|
||||
assert.doesNotMatch(frontend, /provenanceChainHead:\s*project/);
|
||||
|
||||
for (const phase of ["PREPARED", "RECORDS_DURABLE", "LEDGER_APPENDED", "CHAIN_HEAD_ADVANCED", "SQLITE_APPLIED", "COMPLETE", "QUARANTINED", "FAILED"]) {
|
||||
assert.match(native, new RegExp(phase));
|
||||
}
|
||||
for (const capability of ["client_action_id", "LockFileEx", "event_sequence", "SegmentManifest", "VerificationReport", "canonicalize", "normalize_nfc", "RecoveryClassification"]) {
|
||||
assert.match(native, new RegExp(capability));
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps milestone-three crash and concurrency acceptance tests executable", async () => {
|
||||
const tests = await readFile(new URL("../src-tauri/src/provenance/tests.rs", import.meta.url), "utf8");
|
||||
for (const boundary of [
|
||||
"IntentPrepared",
|
||||
"FirstRecordStaged",
|
||||
"RecordFlushed",
|
||||
"RecordMoved",
|
||||
"RecordDirectorySynced",
|
||||
"LedgerAppendBeforeFlush",
|
||||
"LedgerFlushed",
|
||||
"ChainHeadTemporaryWritten",
|
||||
"ChainHeadReplaced",
|
||||
"SegmentManifestFlushed",
|
||||
"SegmentMoved",
|
||||
"NewActiveSegmentCreated",
|
||||
]) {
|
||||
assert.match(tests, new RegExp(boundary));
|
||||
}
|
||||
assert.match(tests, /os_writer_lock_serializes_concurrent_actions/);
|
||||
assert.match(tests, /retries_are_idempotent_and_conflicts_are_rejected/);
|
||||
assert.match(tests, /recovery_rebuilds_sqlite_from_authoritative_events/);
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const source = (path) => readFile(new URL(path, import.meta.url), "utf8");
|
||||
|
||||
test("routes Phase 1 UI state through typed native commands and canonical replay", async () => {
|
||||
const [frontend, native, phase1] = await Promise.all([
|
||||
source("../src/Thinkloom.tsx"),
|
||||
source("../src-tauri/src/lib.rs"),
|
||||
source("../src-tauri/src/provenance/phase1.rs"),
|
||||
]);
|
||||
|
||||
assert.match(frontend, /apply_phase1_command/);
|
||||
assert.match(frontend, /load_phase1_projection/);
|
||||
assert.doesNotMatch(frontend, /persist_state|load_project_state|application-state-snapshot/);
|
||||
assert.doesNotMatch(native, /fn persist_state|fn load_project_state|fn record_cpl_action/);
|
||||
|
||||
for (const operation of [
|
||||
"SessionCreated", "SessionActivated", "SessionTitleRevised", "PersonaChanged",
|
||||
"ChallengeChanged", "GenreChanged", "LoreChanged", "ProviderContextChanged",
|
||||
"CloudApprovalChanged", "HumanTurnCreated", "AssistantTurnCreated", "IdeasChanged",
|
||||
"DraftingPaperTurnAppended", "DraftingPaperRevised", "ProviderInvocationRequested",
|
||||
"ProviderInvocationResponded", "ProviderInvocationFailed", "DistillationDisposed",
|
||||
"ExternalContentDeclared",
|
||||
]) assert.match(phase1, new RegExp(operation));
|
||||
|
||||
for (const recordType of [
|
||||
"transcript-turn", "conversation-session", "idea-revision", "drafting-paper-revision",
|
||||
"invocation-request", "invocation-response", "invocation-failure", "disposition-revision",
|
||||
"source-declaration", "voice-transcription",
|
||||
]) assert.match(phase1, new RegExp(recordType));
|
||||
|
||||
assert.match(phase1, /reconstruct_from_events/);
|
||||
assert.match(phase1, /record_type == "phase1-operation"/);
|
||||
assert.match(phase1, /operational_state: None/);
|
||||
});
|
||||
|
||||
test("records provider intent before I/O and records every outcome afterward", async () => {
|
||||
const native = await source("../src-tauri/src/lib.rs");
|
||||
const generate = native.slice(native.indexOf("fn generate_text("), native.indexOf("#[cfg_attr(mobile", native.indexOf("fn generate_text(")));
|
||||
const request = generate.indexOf("ProviderInvocationRequested");
|
||||
const send = generate.indexOf("request.send()");
|
||||
const response = generate.indexOf("ProviderInvocationResponded");
|
||||
const failure = generate.indexOf("ProviderInvocationFailed");
|
||||
assert.ok(request >= 0 && send > request, "request record must precede provider I/O");
|
||||
assert.ok(response > send, "response record must follow provider I/O");
|
||||
assert.ok(failure > send, "failure record must follow provider I/O");
|
||||
assert.match(generate, /No CPL writer lock is held while provider I\/O runs/);
|
||||
});
|
||||
|
||||
test("treats voice transcription as human text without retaining audio", async () => {
|
||||
const [frontend, phase1] = await Promise.all([
|
||||
source("../src/Thinkloom.tsx"),
|
||||
source("../src-tauri/src/provenance/phase1.rs"),
|
||||
]);
|
||||
assert.match(frontend, /inputMode.*voice_transcription/s);
|
||||
assert.match(phase1, /audio_retained": false/);
|
||||
assert.match(phase1, /audio_reference": Value::Null/);
|
||||
assert.match(phase1, /voice_transcription_retains_text_but_no_audio_reference_or_digest/);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
test("establishes the exact CPL 1.0 project marker and conforming layout", async () => {
|
||||
const [format, native, writer] = await Promise.all([
|
||||
readFile(new URL("../src-tauri/src/project_format.rs", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src-tauri/src/lib.rs", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src-tauri/src/provenance/writer.rs", import.meta.url), "utf8"),
|
||||
]);
|
||||
|
||||
for (const exact of [
|
||||
'PROJECT_FORMAT: &str = "thinkloom-cpl"',
|
||||
'PROJECT_FORMAT_VERSION: &str = "1.0"',
|
||||
'PROVENANCE_CONFORMANCE: &str = "cpl-1.0"',
|
||||
]) assert.match(format, new RegExp(exact.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
||||
|
||||
for (const path of ["records", "provenance/ledger/active", "provenance/ledger/sealed", "reports", ".app/locks", ".app/temp", ".app/recovery"]) {
|
||||
assert.match(format, new RegExp(path.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
||||
}
|
||||
assert.match(native, /project_format: project_format::PROJECT_FORMAT/);
|
||||
assert.match(native, /project_format_version: project_format::PROJECT_FORMAT_VERSION/);
|
||||
assert.match(native, /provenance_conformance: project_format::PROVENANCE_CONFORMANCE/);
|
||||
assert.match(writer, /\.app\/locks/);
|
||||
assert.match(writer, /\.app\/temp\/staging/);
|
||||
assert.doesNotMatch(`${native}\n${writer}`, /\.thinkloom/);
|
||||
});
|
||||
|
||||
test("classifies legacy projects before recovery and exposes preservation-only controls", async () => {
|
||||
const [format, native, frontend] = await Promise.all([
|
||||
readFile(new URL("../src-tauri/src/project_format.rs", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src-tauri/src/lib.rs", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src/Thinkloom.tsx", import.meta.url), "utf8"),
|
||||
]);
|
||||
|
||||
assert.match(format, /LegacyPreviewReadOnly/);
|
||||
assert.match(format, /schema_version_without_marker_is_legacy_and_inspection_is_read_only/);
|
||||
assert.match(format, /preservation_archive_retains_source_bytes_without_changing_source/);
|
||||
assert.match(format, /Not verified, converted, or CPL-conforming/);
|
||||
assert.match(format, /LEGACY_ARCHIVE_INSIDE_PROJECT/);
|
||||
|
||||
const inspect = native.indexOf("project_format::inspect_project(&root)");
|
||||
const recover = native.indexOf("CplService::new(&root, &manifest.project_id).recover()", inspect);
|
||||
assert.ok(inspect >= 0 && recover > inspect, "project inspection must precede CPL recovery");
|
||||
assert.match(native, /inspection\.classification != project_format::ProjectClassification::CplConforming/);
|
||||
assert.match(native, /set_read_only_project/);
|
||||
assert.match(native, /show_project_folder/);
|
||||
assert.match(native, /create_legacy_preservation_archive/);
|
||||
assert.match(native, /LEGACY_BACKUP_REFUSED/);
|
||||
|
||||
assert.match(frontend, /Legacy preview project/);
|
||||
assert.match(frontend, /Show project folder/);
|
||||
assert.match(frontend, /Create preservation archive/);
|
||||
assert.match(frontend, /No migration was attempted/);
|
||||
});
|
||||
@@ -24,43 +24,74 @@ function validator() {
|
||||
}
|
||||
|
||||
const requiredSchemas = [
|
||||
"assertion-evaluation", "backup-manifest", "chain-head", "content-reference", "conversation-session", "derived-index-manifest",
|
||||
"disposition-revision", "edit-transaction", "encrypted-key-envelope", "idea", "idea-revision",
|
||||
"assertion-evaluation", "backup-manifest", "chain-head", "composition-operation", "content-reference", "contribution-map",
|
||||
"conversation-session", "deposit-snapshot", "derived-index-manifest", "disposition-revision", "edit-transaction",
|
||||
"encrypted-key-envelope", "expression-segment", "harp-export-manifest", "human-authorship-record", "idea", "idea-revision",
|
||||
"invocation-failure", "invocation-request", "invocation-response", "invocation-state", "invocation-stream-state",
|
||||
"invocation-stream-summary", "ledger-segment-manifest", "manuscript-revision", "model-capability-snapshot",
|
||||
"model-configuration-snapshot", "project-key-manifest", "project-manifest", "prompt-template",
|
||||
"prompt-template-reference", "provenance-assertion", "provenance-event", "provenance-policy", "purge-manifest", "record-envelope",
|
||||
"recovery-key-envelope", "release-manifest", "release-state", "sanitized-export-manifest", "text-fragment-reference",
|
||||
"transcript-correction", "transcript-normalization", "transcript-turn", "verification-report", "write-intent",
|
||||
"recovery-key-envelope", "registration-policy-profile", "release-manifest", "release-state", "sanitized-export-manifest",
|
||||
"text-fragment-reference", "transcript-correction", "transcript-normalization", "transcript-turn", "verification-report", "write-intent",
|
||||
];
|
||||
|
||||
test("catalogs every approved Stage 2 schema under Draft 2020-12", () => {
|
||||
assert.equal(catalog.catalog_version, "1.0");
|
||||
assert.equal(catalog.catalog_version, "1.1");
|
||||
assert.equal(catalog.package_version, "0.5.2");
|
||||
assert.equal(catalog.provenance_schema_version, "1.0");
|
||||
assert.equal(catalog.application_version, "0.4.0");
|
||||
assert.deepEqual(catalog.compatible_application_versions, ["0.4.0"]);
|
||||
assert.equal(catalog.application_version, "0.5.2");
|
||||
assert.deepEqual(catalog.compatible_application_versions, ["0.4.0", "0.5.0", "0.5.1", "0.5.2", "0.5.3", "0.5.4", "0.5.5", "0.5.6", "0.5.7", "0.5.8", "0.5.9", "0.5.10", "0.5.11", "0.6.0"]);
|
||||
assert.equal(catalog.cpl_runtime_target, "0.6.0");
|
||||
assert.equal(catalog.native_writer_conformance, false);
|
||||
assert.match(catalog.assertion_semantics_compatibility, /v0\.4.+remain valid/i);
|
||||
assert.equal(catalog.dialect, "https://json-schema.org/draft/2020-12/schema");
|
||||
assert.deepEqual(catalog.schemas.map(({ name }) => name), requiredSchemas);
|
||||
assert.equal(new Set(catalog.schemas.map(({ id }) => id)).size, requiredSchemas.length);
|
||||
for (const entry of catalog.schemas) {
|
||||
const isCompositionExtension = ["composition-operation", "expression-segment", "contribution-map", "deposit-snapshot", "registration-policy-profile", "human-authorship-record", "harp-export-manifest"].includes(entry.name);
|
||||
assert.equal(entry.introduced_in_application_version, isCompositionExtension ? "0.5.2" : "0.4.0");
|
||||
assert.ok(entry.compatible_application_versions.includes("0.5.3"));
|
||||
assert.ok(entry.compatible_application_versions.includes("0.5.10"));
|
||||
assert.ok(entry.compatible_application_versions.includes("0.5.11"));
|
||||
assert.ok(entry.compatible_application_versions.includes("0.6.0"));
|
||||
if (!isCompositionExtension) assert.ok(entry.compatible_application_versions.includes("0.4.0"));
|
||||
}
|
||||
for (const { schema } of schemaEntries) {
|
||||
assert.equal(schema.additionalProperties, false, `${schema.$id} must be closed at its top level`);
|
||||
}
|
||||
});
|
||||
|
||||
test("publishes complete versioned assertion registries", async () => {
|
||||
const expected = ["assertion-boundary-kinds", "assertion-confidence-dimensions", "assertion-evaluation-statuses", "assertion-evidence-classes", "assertion-lifecycle-phases", "assertion-reason-codes"];
|
||||
assert.deepEqual(catalog.registries.map(({ name }) => name).sort(), expected);
|
||||
test("publishes complete versioned assertion and composition registries", async () => {
|
||||
const legacyExpected = ["assertion-boundary-kinds", "assertion-confidence-dimensions", "assertion-evaluation-statuses", "assertion-evidence-classes", "assertion-lifecycle-phases", "assertion-reason-codes"];
|
||||
const compositionExpected = ["composition-assertion-predicates", "composition-operation-kinds", "contribution-map-layers", "harp-explanation-codes", "harp-limitation-codes", "recorded-origin-kinds", "registration-treatment-suggestions", "transformation-relationships"];
|
||||
assert.deepEqual(catalog.registries.map(({ name }) => name).sort(), [...legacyExpected, ...compositionExpected].sort());
|
||||
for (const entry of catalog.registries) {
|
||||
const registry = await readJson(entry.file);
|
||||
assert.equal(registry.registry_version, "1.0");
|
||||
const isCompositionExtension = compositionExpected.includes(entry.name);
|
||||
assert.equal(registry.registry_version, isCompositionExtension ? "1.1" : "1.0");
|
||||
assert.equal(registry.provenance_schema_version, "1.0");
|
||||
assert.equal(registry.application_version, "0.4.0");
|
||||
assert.equal(registry.application_version, isCompositionExtension ? "0.5.2" : "0.4.0");
|
||||
assert.equal(registry.introduced_in_application_version, isCompositionExtension ? "0.5.2" : "0.4.0");
|
||||
assert.ok(registry.compatible_application_versions.includes("0.5.3"));
|
||||
assert.ok(registry.compatible_application_versions.includes("0.5.10"));
|
||||
assert.ok(registry.compatible_application_versions.includes("0.5.11"));
|
||||
assert.ok(registry.compatible_application_versions.includes("0.6.0"));
|
||||
assert.ok(registry.entries.length > 0);
|
||||
assert.ok(registry.entries.every(({ meaning }) => typeof meaning === "string" && meaning.length > 0));
|
||||
assert.equal(new Set(registry.entries.map(({ code }) => code)).size, registry.entries.length);
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps composition dimensions independent and preserves v0.4 assertion semantics", async () => {
|
||||
const registries = Object.fromEntries(await Promise.all(catalog.registries.map(async (entry) => [entry.name, await readJson(entry.file)])));
|
||||
assert.deepEqual(registries["composition-operation-kinds"].entries.map(({ code }) => code), ["insert", "delete", "replace", "move", "paste", "transcription", "ai_acceptance", "restoration"]);
|
||||
assert.deepEqual(registries["recorded-origin-kinds"].entries.map(({ code }) => code), ["recorded_direct_human_input", "human_expressive_input_via_transcription", "accepted_ai_output", "imported_or_pasted", "system_restoration", "unattested"]);
|
||||
assert.deepEqual(registries["composition-assertion-predicates"].entries.map(({ code }) => code), ["derived_from", "generated_by", "modified_by_human", "selected_by_human", "arranged_by_human", "included_in_deposit"]);
|
||||
assert.ok(registries["contribution-map-layers"].entries.some(({ code }) => code === "selection_arrangement"));
|
||||
assert.ok(registries["registration-treatment-suggestions"].entries.some(({ code }) => code === "manual_review_required"));
|
||||
assert.deepEqual(registries["assertion-confidence-dimensions"].entries.map(({ code }) => code), ["integrity", "identity", "chronology", "derivation", "authorship", "completeness"]);
|
||||
assert.equal(registries["assertion-confidence-dimensions"].application_version, "0.4.0");
|
||||
});
|
||||
test("accepts every valid fixture", async () => {
|
||||
const ajv = validator();
|
||||
for (const entry of schemaEntries) {
|
||||
@@ -200,6 +231,11 @@ test("reproduces every defined self-digest identity", async () => {
|
||||
assert.equal(sha256(vector.protected_record.identity), vector.protected_record.digest);
|
||||
assert.equal(releaseMerkleRoot(vector.release_manifest.merkle_identity), vector.release_manifest.digest);
|
||||
assert.ok(vector.release_manifest.excluded_paths.includes("release-manifest.json"));
|
||||
for (const [key, schemaName] of [["contribution_map", "contribution-map"], ["registration_policy_profile", "registration-policy-profile"], ["human_authorship_record", "human-authorship-record"], ["harp_export_manifest", "harp-export-manifest"]]) {
|
||||
assert.equal(sha256(vector[key].identity), vector[key].digest, key);
|
||||
const validate = ajv.getSchema(`https://thinkloom.app/schemas/provenance/1.0/${schemaName}.schema.json`);
|
||||
assert.equal(validate(vector[key].complete_record), true, `${key}: ${ajv.errorsText(validate.errors)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("validates key recovery materials and sanitized non-mutating export disclosure", async () => {
|
||||
@@ -215,6 +251,20 @@ test("validates key recovery materials and sanitized non-mutating export disclos
|
||||
const validateSanitized = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/sanitized-export-manifest.schema.json");
|
||||
assert.equal(validateSanitized(sanitizedVector.manifest), true, ajv.errorsText(validateSanitized.errors));
|
||||
assert.equal(sha256(sanitizedVector.manifest.omission_rules), sanitizedVector.manifest.rules_sha256);
|
||||
assert.equal(sanitizedVector.manifest.completeness_claim, "selective_disclosed_subset");
|
||||
assert.deepEqual(sanitizedVector.manifest.omission_rules.map((rule) => rule.category), [
|
||||
"private_conversation",
|
||||
"rejected_model_output",
|
||||
"credential_authorization_material",
|
||||
"personal_identifier",
|
||||
"internal_path",
|
||||
"provider_metadata_not_required",
|
||||
"protected_source_body",
|
||||
]);
|
||||
for (const rule of sanitizedVector.manifest.omission_rules) {
|
||||
const { disclosure_sha256, ...identity } = rule;
|
||||
assert.equal(sha256(identity), disclosure_sha256);
|
||||
}
|
||||
assert.equal(sanitizedVector.source_chain_head_before, sanitizedVector.source_chain_head_after);
|
||||
assert.ok(sanitizedVector.exported_record_count < sanitizedVector.source_record_count);
|
||||
});
|
||||
@@ -319,3 +369,92 @@ test("keeps assertion status, reason, confidence, and evidence semantics registr
|
||||
assert.deepEqual(evidenceRegistry.entries.map(({ code }) => code), ["mandatory_live", "mandatory_retained", "advisory", "shadow"]);
|
||||
assert.deepEqual(evidenceRegistry.entries.map(({ exact_effect }) => exact_effect), ["required", "required", "may_degrade", "no_authority"]);
|
||||
});
|
||||
test("enforces composition-operation origin rules and refuses unknown exact classifications", async () => {
|
||||
const ajv = validator();
|
||||
const vector = await readJson("vectors", "composition-and-harp-classification.json");
|
||||
const validateOperation = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/composition-operation.schema.json");
|
||||
const validateSegment = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/expression-segment.schema.json");
|
||||
const validateHarp = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/human-authorship-record.schema.json");
|
||||
|
||||
for (const [name, operation] of Object.entries(vector.operations)) {
|
||||
assert.equal(validateOperation(operation), true, `${name}: ${ajv.errorsText(validateOperation.errors)}`);
|
||||
}
|
||||
assert.equal(vector.operations.paste.recorded_origin_kind, "imported_or_pasted");
|
||||
assert.notEqual(vector.operations.paste.recorded_origin_kind, "recorded_direct_human_input");
|
||||
assert.equal(vector.operations.ai_acceptance.recorded_origin_kind, "accepted_ai_output");
|
||||
assert.ok(vector.operations.ai_acceptance.invocation_id);
|
||||
assert.ok(vector.operations.ai_acceptance.disposition_id);
|
||||
|
||||
assert.equal(validateSegment(vector.exact_expression_segment), true, ajv.errorsText(validateSegment.errors));
|
||||
assert.equal(validateHarp(vector.exact_harp), true, ajv.errorsText(validateHarp.errors));
|
||||
for (const [name, instance] of Object.entries(vector.forbidden_exact_segments)) {
|
||||
assert.equal(validateSegment(instance), false, `segment ${name} must not validate as exact`);
|
||||
}
|
||||
for (const [name, instance] of Object.entries(vector.forbidden_exact_harps)) {
|
||||
assert.equal(validateHarp(instance), false, `HARP ${name} must not validate as exact`);
|
||||
}
|
||||
assert.deepEqual(vector.independent_dimensions, ["recorded_origin", "transformation", "selection_arrangement", "evidentiary_evaluation", "suggested_registration_treatment"]);
|
||||
for (const field of vector.prohibited_claim_fields) assert.equal(Object.hasOwn(vector.exact_harp, field), false, field);
|
||||
});
|
||||
|
||||
test("reproduces complete ordered contribution maps independently of input order", async () => {
|
||||
const ajv = validator();
|
||||
const vector = await readJson("vectors", "contribution-map-determinism.json");
|
||||
const validate = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/contribution-map.schema.json");
|
||||
assert.equal(validate(vector.deterministic_map), true, ajv.errorsText(validate.errors));
|
||||
|
||||
const sortSegments = (segments) => [...segments].sort((left, right) => left.segment_sequence - right.segment_sequence || (left.segment_id < right.segment_id ? -1 : left.segment_id > right.segment_id ? 1 : 0));
|
||||
const canonicalOrders = vector.input_orders.map((segments) => canonicalize(sortSegments(segments)));
|
||||
assert.equal(canonicalOrders[0], canonicalOrders[1]);
|
||||
assert.equal(sha256(canonicalize(vector.deterministic_map)), vector.canonical_map_sha256);
|
||||
const { deterministic_map: map } = vector;
|
||||
const mapIdentity = { ...map };
|
||||
delete mapIdentity.contribution_map_sha256;
|
||||
assert.equal(sha256(mapIdentity), map.contribution_map_sha256);
|
||||
assert.equal(map.coverage.coverage_status, "complete");
|
||||
assert.equal(map.coverage.recorded_positions, map.coverage.denominator);
|
||||
|
||||
let cursor = 0;
|
||||
for (const segment of map.segments) {
|
||||
assert.equal(segment.range.coordinate_system, "unicode_scalar");
|
||||
assert.equal(segment.range.start, cursor, `${segment.segment_id} must begin at the prior end`);
|
||||
assert.ok(segment.range.end > segment.range.start);
|
||||
assert.equal(segment.range.end - segment.range.start, segment.normalized_unicode_scalar_length);
|
||||
cursor = segment.range.end;
|
||||
}
|
||||
assert.equal(cursor, map.coverage.denominator);
|
||||
assert.ok(map.layers.includes("selection_arrangement"));
|
||||
assert.ok(map.layers.includes("recorded_origin"));
|
||||
});
|
||||
|
||||
test("binds HARP to one exact deposit and makes later revisions stale without rewriting history", async () => {
|
||||
const ajv = validator();
|
||||
const vector = await readJson("vectors", "harp-deposit-staleness.json");
|
||||
const validateDeposit = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/deposit-snapshot.schema.json");
|
||||
const validateHarp = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/human-authorship-record.schema.json");
|
||||
const validateManifest = ajv.getSchema("https://thinkloom.app/schemas/provenance/1.0/harp-export-manifest.schema.json");
|
||||
assert.equal(validateDeposit(vector.deposit_snapshot), true, ajv.errorsText(validateDeposit.errors));
|
||||
assert.equal(validateHarp(vector.current_harp), true, ajv.errorsText(validateHarp.errors));
|
||||
assert.equal(validateHarp(vector.stale_after_edit), true, ajv.errorsText(validateHarp.errors));
|
||||
assert.equal(validateManifest(vector.export_manifest), true, ajv.errorsText(validateManifest.errors));
|
||||
|
||||
assert.equal(vector.current_harp.deposit.deposit_sha256, vector.deposit_snapshot.deposit_sha256);
|
||||
assert.equal(vector.current_harp.deposit.manuscript_revision_id, vector.deposit_snapshot.manuscript_revision_id);
|
||||
assert.equal(vector.current_harp.cpl_binding.chain_head, vector.deposit_snapshot.cpl_chain_head);
|
||||
assert.equal(vector.current_harp.applicability_status, "current");
|
||||
assert.equal(vector.stale_after_edit.applicability_status, "stale");
|
||||
assert.notEqual(vector.stale_after_edit.deposit.manuscript_revision_sha256, vector.current_harp.deposit.manuscript_revision_sha256);
|
||||
assert.equal(vector.current_harp.suggested_registration_language.user_approved, true);
|
||||
|
||||
for (const [record, digestField] of [[vector.current_harp, "harp_sha256"], [vector.stale_after_edit, "harp_sha256"], [vector.export_manifest, "manifest_sha256"]]) {
|
||||
const identity = { ...record };
|
||||
delete identity[digestField];
|
||||
assert.equal(sha256(identity), record[digestField]);
|
||||
}
|
||||
const fileByRole = Object.fromEntries(vector.export_manifest.files.map((file) => [file.role, file]));
|
||||
assert.equal(fileByRole.machine_readable_harp.sha256, vector.current_harp.harp_sha256);
|
||||
assert.equal(fileByRole.deposit_copy.sha256, vector.deposit_snapshot.deposit_sha256);
|
||||
assert.equal(vector.export_manifest.harp_sha256, vector.current_harp.harp_sha256);
|
||||
assert.equal(vector.export_manifest.deposit_sha256, vector.deposit_snapshot.deposit_sha256);
|
||||
assert.doesNotMatch(JSON.stringify({ claim_summary: vector.current_harp.claim_summary, coverage: vector.current_harp.coverage, language: vector.current_harp.suggested_registration_language }), /human\s*(?:percentage|%)|ai\s*(?:percentage|%)|copyright verified|originality proven|authorship certified/i);
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const source = (path) => readFile(new URL(path, import.meta.url), "utf8");
|
||||
|
||||
test("connects the milestone 9 CPL explorer and HARP preparation views", async () => {
|
||||
const [app, workspace, native, modules] = await Promise.all([
|
||||
source("../src/Thinkloom.tsx"),
|
||||
source("../src/ProvenanceWorkspace.tsx"),
|
||||
source("../src-tauri/src/lib.rs"),
|
||||
source("../src-tauri/src/provenance/mod.rs"),
|
||||
]);
|
||||
|
||||
assert.match(app, /import ProvenanceWorkspace/);
|
||||
assert.match(app, /<ProvenanceWorkspace onNotice=/);
|
||||
assert.match(modules, /pub mod explorer;/);
|
||||
assert.match(native, /fn load_cpl_explorer/);
|
||||
assert.match(native, /fn prepare_harp/);
|
||||
assert.match(workspace, /CPL explorer/);
|
||||
assert.match(workspace, /Prepare HARP/);
|
||||
assert.match(workspace, /load_cpl_explorer/);
|
||||
assert.match(workspace, /prepare_harp/);
|
||||
});
|
||||
|
||||
test("renders native evidence, lineage, evaluation, and verification boundaries", async () => {
|
||||
const workspace = await source("../src/ProvenanceWorkspace.tsx");
|
||||
|
||||
for (const requirement of [
|
||||
"Native CPL verification",
|
||||
"Composition timeline",
|
||||
"Expression lineage",
|
||||
"Assertions and current evaluations",
|
||||
"Underlying records",
|
||||
"Exact, degraded, stale, and unverified",
|
||||
"Statement → assertion → evaluation → record",
|
||||
]) assert.match(workspace, new RegExp(requirement));
|
||||
|
||||
assert.doesNotMatch(workspace, /human_percentage/);
|
||||
assert.match(workspace, /not a human-authorship score/);
|
||||
});
|
||||
|
||||
test("keeps evidence categories and legal limits explicit", async () => {
|
||||
const workspace = await source("../src/ProvenanceWorkspace.tsx");
|
||||
|
||||
for (const category of [
|
||||
"Evidence fact",
|
||||
"User declaration",
|
||||
"Derived classification",
|
||||
"Suggested application language",
|
||||
"Legal determination not made",
|
||||
]) assert.match(workspace, new RegExp(category));
|
||||
|
||||
assert.match(workspace, /does not determine legal authorship, originality, copyrightability, ownership, or registrability/);
|
||||
});
|
||||
|
||||
test("requires explicit review and approval before native HARP generation", async () => {
|
||||
const workspace = await source("../src/ProvenanceWorkspace.tsx");
|
||||
|
||||
for (const action of [
|
||||
"Freeze or select the exact deposit",
|
||||
"Confirm the author identity declaration",
|
||||
"Review AI systems used",
|
||||
"Review final-text classifications",
|
||||
"Resolve or accept evidence boundaries",
|
||||
"Preview suggested registration language",
|
||||
"Choose the archive and explicitly approve",
|
||||
"I explicitly approve HARP generation",
|
||||
"Approve and generate HARP",
|
||||
]) assert.match(workspace, new RegExp(action));
|
||||
|
||||
assert.match(workspace, /userApproved: true/);
|
||||
assert.match(workspace, /generate_harp/);
|
||||
assert.match(workspace, /disabled=\{!approved \|\| busy\}/);
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const source = (relative) => readFile(path.join(root, relative), "utf8");
|
||||
|
||||
test("maps every Milestone 11 requirement to executable verification", async () => {
|
||||
const files = [
|
||||
"tests/provenance-schema.test.mjs",
|
||||
"tests/native-cpl.test.mjs",
|
||||
"tests/phase1-provenance.test.mjs",
|
||||
"tests/composition-provenance.test.mjs",
|
||||
"tests/contribution-map.test.mjs",
|
||||
"tests/harp-generation.test.mjs",
|
||||
"tests/provenance-ui.test.mjs",
|
||||
"tests/harp-export.test.mjs",
|
||||
"src-tauri/src/provenance/tests.rs",
|
||||
"src-tauri/src/provenance/composition.rs",
|
||||
"src-tauri/src/provenance/contribution_map.rs",
|
||||
"src-tauri/src/provenance/harp.rs",
|
||||
"src-tauri/src/provenance/export.rs",
|
||||
"src-tauri/src/provenance/phase1.rs",
|
||||
"src-tauri/src/provenance/verifier.rs",
|
||||
"src/Thinkloom.tsx",
|
||||
];
|
||||
const corpus = (await Promise.all(files.map(source))).join("\n");
|
||||
const requirements = new Map([
|
||||
["schema fixtures and deterministic regeneration", /(?=[\s\S]*accepts every valid fixture)(?=[\s\S]*rejects every invalid fixture)(?=[\s\S]*deterministic derived-index)/i],
|
||||
["canonical JSON, Unicode, timestamp, path, and JSONL vectors", /(?=[\s\S]*canonical JSON)(?=[\s\S]*canonical timestamps)(?=[\s\S]*repository-relative paths)(?=[\s\S]*JSONL)/i],
|
||||
["duplicate actions and concurrent writers", /(?=[\s\S]*retries_are_idempotent_and_conflicts_are_rejected)(?=[\s\S]*os_writer_lock_serializes_concurrent_actions)/],
|
||||
["failure after every durable write phase", /(?=[\s\S]*every_durable_boundary_recovers_and_retries_safely)(?=[\s\S]*every_segment_rotation_boundary_recovers_without_sequence_loss)/],
|
||||
["segment rotation and cross-segment verification", /rotates_and_verifies_cross_segment_linkage/],
|
||||
["manual typing and deletion", /(?=[\s\S]*typed_records_reconstruct_phase1)(?=[\s\S]*transform_delete)/],
|
||||
["paste and import", /(?=[\s\S]*ImportedOrPasted)(?=[\s\S]*onPasteCapture)/],
|
||||
["human revision of AI-origin material", /transform_revise/],
|
||||
["AI transformation of human material", /transform_ai/],
|
||||
["partial AI acceptance", /unicode_scalar_diff_and_partial_ai_ranges_are_exact/],
|
||||
["selection and arrangement without origin changes", /selection_and_arrangement_preserve_each_source_origin/],
|
||||
["voice transcription without audio persistence", /voice_transcription_retains_text_but_no_audio_reference_or_digest/],
|
||||
["restore and checkpoint lineage", /(?=[\s\S]*replays_manual_paste_ai_revision_and_restoration_with_lineage)(?=[\s\S]*create_checkpoint)/],
|
||||
["unknown and unattested spans", /reports_unattested_coverage_without_calling_it_non_human/],
|
||||
["deposit and HARP staleness", /(?=[\s\S]*binds HARP to one exact deposit)(?=[\s\S]*frozen_map_becomes_stale)/],
|
||||
["complete segment coverage across complex Unicode", /complex_unicode_has_complete_contiguous_segment_coverage/],
|
||||
["native verifier and frontend consistency", /(?=[\s\S]*require_release_verification)(?=[\s\S]*verify_provenance)(?=[\s\S]*VERIFIED_WITH_WARNINGS)/],
|
||||
["sanitized archive disclosure", /discloses and hash-binds every required sanitized omission category/],
|
||||
["release blocking", /release_gate_accepts_only_complete_safe_native_verification/],
|
||||
]);
|
||||
for (const [requirement, pattern] of requirements) {
|
||||
assert.match(corpus, pattern, `Missing executable coverage: ${requirement}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("prohibits scores and affirmative legal conclusions in shipped source", async () => {
|
||||
const roots = ["src", "src-tauri/src"];
|
||||
const productionFiles = [];
|
||||
for (const directory of roots) {
|
||||
const entries = await readdir(path.join(root, directory), { recursive: true, withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile() || !/\.(?:rs|tsx|ts|css)$/.test(entry.name)) continue;
|
||||
productionFiles.push(path.join(entry.parentPath, entry.name));
|
||||
}
|
||||
}
|
||||
const corpus = (await Promise.all(productionFiles.map((file) => readFile(file, "utf8")))).join("\n");
|
||||
for (const prohibited of [
|
||||
/\b\d+(?:\.\d+)?%\s+human\b/i,
|
||||
/human[- ]authorship\s+(?:score|percentage)\s*[:=]\s*\d/i,
|
||||
/Thinkloom\s+(?:determines|certifies|proves)\s+(?:legal authorship|originality|copyrightability|ownership|registrability)/i,
|
||||
/(?:legal authorship|copyrightability)\s*[:=]\s*["']?(?:yes|verified|valid)/i,
|
||||
]) assert.doesNotMatch(corpus, prohibited);
|
||||
});
|
||||
|
||||
test("uses the same native verification statuses in the frontend release gate", async () => {
|
||||
const [records, frontend, verifier, native] = await Promise.all([
|
||||
source("src-tauri/src/provenance/records.rs"),
|
||||
source("src/Thinkloom.tsx"),
|
||||
source("src-tauri/src/provenance/verifier.rs"),
|
||||
source("src-tauri/src/lib.rs"),
|
||||
]);
|
||||
for (const status of ["VERIFIED", "VERIFIED_WITH_WARNINGS", "INCOMPLETE", "FAILED", "UNSAFE"]) {
|
||||
assert.match(frontend, new RegExp(`\\b${status}\\b`));
|
||||
}
|
||||
assert.match(records, /VerifiedWithWarnings[\s\S]*Incomplete[\s\S]*Failed[\s\S]*Unsafe/);
|
||||
assert.match(verifier, /Verified \| VerificationStatus::VerifiedWithWarnings => Ok/);
|
||||
assert.match(native, /verify_project\(&root, &manifest\.project_id\)[\s\S]*require_release_verification/);
|
||||
assert.doesNotMatch(frontend, /status\s*===\s*["']VALID["']/);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { access, readFile, readdir, stat } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const releaseRoot = path.join(root, "src-tauri", "target", "release");
|
||||
const packageJson = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
|
||||
const tauriConfig = JSON.parse(await readFile(path.join(root, "src-tauri", "tauri.conf.json"), "utf8"));
|
||||
|
||||
test("packaged Windows release launches and includes both installer formats", { skip: process.platform !== "win32", timeout: 20_000 }, async () => {
|
||||
assert.equal(tauriConfig.version, packageJson.version);
|
||||
assert.deepEqual(new Set(tauriConfig.bundle.targets), new Set(["msi", "nsis"]));
|
||||
|
||||
const executable = path.join(releaseRoot, "thinkloom.exe");
|
||||
await access(executable);
|
||||
const executableBytes = await readFile(executable);
|
||||
assert.deepEqual(executableBytes.subarray(0, 2), Buffer.from("MZ"));
|
||||
assert.ok((await stat(executable)).size > 1_000_000, "release executable is unexpectedly small");
|
||||
|
||||
const bundleRoot = path.join(releaseRoot, "bundle");
|
||||
const entries = await readdir(bundleRoot, { recursive: true, withFileTypes: true });
|
||||
const artifacts = entries
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => path.join(entry.parentPath, entry.name));
|
||||
const msi = artifacts.find((file) => file.toLowerCase().endsWith(".msi") && file.includes(packageJson.version));
|
||||
const nsis = artifacts.find((file) => file.toLowerCase().endsWith("setup.exe") && file.includes(packageJson.version));
|
||||
assert.ok(msi, `no ${packageJson.version} MSI bundle found`);
|
||||
assert.ok(nsis, `no ${packageJson.version} NSIS bundle found`);
|
||||
assert.deepEqual((await readFile(msi)).subarray(0, 8), Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]));
|
||||
assert.deepEqual((await readFile(nsis)).subarray(0, 2), Buffer.from("MZ"));
|
||||
|
||||
const child = spawn(executable, [], { cwd: releaseRoot, windowsHide: true, stdio: "ignore" });
|
||||
await new Promise((resolve, reject) => {
|
||||
child.once("error", reject);
|
||||
child.once("spawn", resolve);
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 2_500));
|
||||
if (child.exitCode === null) {
|
||||
assert.equal(child.kill(), true, "packaged application could not be stopped after launch smoke test");
|
||||
await new Promise((resolve) => child.once("exit", resolve));
|
||||
} else {
|
||||
assert.equal(child.exitCode, 0, "packaged application exited unsuccessfully during launch smoke test");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user