diff --git a/ci/required-files.txt b/ci/required-files.txt index e9d3956..6695ca7 100644 --- a/ci/required-files.txt +++ b/ci/required-files.txt @@ -98,3 +98,7 @@ core/v1/generations/g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8d scripts/test/portable-core-ingress-fixtures.json scripts/test/portable-core-ingress-ledger.tsv scripts/test/portable-core-ingress.test.sh +core/v1/generations/g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8da2c386/modules/profile_graph.jq +scripts/test/portable-core-profile-graph-fixtures.jq +scripts/test/portable-core-profile-graph-ledger.tsv +scripts/test/portable-core-profile-graph.test.sh diff --git a/core/v1/generations/g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8da2c386/modules/profile_graph.jq b/core/v1/generations/g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8da2c386/modules/profile_graph.jq new file mode 100644 index 0000000..48fd185 --- /dev/null +++ b/core/v1/generations/g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8da2c386/modules/profile_graph.jq @@ -0,0 +1,315 @@ +import "schema" as schema; + +def optional_ok($name; value_ok): + (has($name) | not) or (.[$name] | value_ok); + +def model_request_ok: + schema::exact_fields(["provider_id","model_id","effort_id"];[]) and + (.provider_id | schema::id_ok) and + (.model_id | schema::id_ok) and + (.effort_id | schema::id_ok); + +def adapter_manifest_body_ok: + schema::exact_fields( + ["adapter_version","package_ref","offered_roles","offered_execution_kinds", + "offered_capabilities","offered_permissions","offered_tools"]; + ["config_contract_ref"]) and + (.adapter_version | schema::version_ok) and + (.package_ref | schema::git_object_ref_ok) and + (.offered_roles | schema::enum_set_ok(1;8;schema::adapter_roles)) and + (.offered_execution_kinds | schema::enum_set_ok(1;2;schema::execution_kinds)) and + (.offered_capabilities | schema::enum_set_ok(0;3;schema::capability_ids)) and + (.offered_permissions | schema::enum_set_ok(0;5;schema::permission_ids)) and + (.offered_tools | schema::bounded_set(0;32;schema::tool_ref_ok;.tool_id)) and + optional_ok("config_contract_ref";schema::scope_ref_purpose_ok("config-contract")); + +def adapter_manifest_shape_ok: + schema::envelope_ok("adapter_manifest") and + (.body | adapter_manifest_body_ok); + +def adapter_manifest_self_ok: adapter_manifest_shape_ok; + +def binding_capability_ok: + . as $binding | + (schema::capabilities_for_role($binding.role)) as $capabilities | + if ($capabilities | length) == 1 then + $binding.requested_capabilities == $capabilities and + $binding.requested_permissions == + schema::permissions_for_capability($capabilities[0];$binding.execution_kind) + else + $binding.requested_capabilities == [] and + $binding.requested_permissions == [] + end; + +def profile_binding_shape_ok: + schema::exact_fields( + ["binding_id","role","manifest_ref","execution_kind","adapter_instance_id", + "principal_id","execution_boundary_id","package_ref","skill_refs", + "requested_tools","requested_capabilities","requested_permissions"]; + ["authority_ref","config_ref","prompt_ref","model_request"]) and + (.binding_id | schema::id_ok) and + (.role | schema::adapter_role_ok) and + (.manifest_ref | schema::document_ref_kind_ok("adapter_manifest")) and + (.execution_kind | schema::execution_kind_ok) and + (.execution_kind as $kind | + .role as $role | + schema::execution_kinds_for_role($role) | index($kind) != null) and + (.adapter_instance_id | schema::id_ok) and + (.principal_id | schema::id_ok) and + (.execution_boundary_id | schema::id_ok) and + optional_ok("authority_ref";schema::scope_ref_purpose_ok("authority")) and + (.package_ref | schema::git_object_ref_ok) and + optional_ok("config_ref";schema::git_object_ref_ok) and + optional_ok("prompt_ref";schema::git_object_ref_ok) and + (.skill_refs | schema::bounded_set(0;32;schema::git_object_ref_ok;schema::git_key)) and + (.requested_tools | schema::bounded_set(0;32;schema::tool_ref_ok;.tool_id)) and + optional_ok("model_request";model_request_ok) and + (.requested_capabilities | + schema::enum_set_ok(0;1;schema::capability_ids)) and + (.requested_permissions | + schema::enum_set_ok(0;5;schema::permission_ids)) and + (if .execution_kind == "model" then + has("model_request") and has("prompt_ref") + else + (has("model_request") | not) and + (has("prompt_ref") | not) and + .skill_refs == [] + end); + +def profile_binding_ok: + profile_binding_shape_ok and binding_capability_ok; + +def profile_body_shape_ok: + schema::exact_fields(["profile_version","bindings"];[]) and + (.profile_version | schema::version_ok) and + (.bindings | + schema::bounded_set(4;8;profile_binding_ok;.binding_id)); + +def profile_body_ok: profile_body_shape_ok; + +def protected_role_separation_ok($bindings): + [$bindings[] | + select(.role as $role | schema::protected_roles | index($role) != null)] as $protected | + [$bindings[] | + select(.role as $role | schema::protected_roles | index($role) == null)] as $other | + all(schema::protected_roles[]; . as $role | + [$protected[] | select(.role == $role)] | length == 1) and + all($protected[]; has("authority_ref")) and + (($protected | map(.binding_id) | unique | length) == ($protected | length)) and + (($protected | map(.adapter_instance_id) | unique | length) == ($protected | length)) and + (($protected | map(.principal_id) | unique | length) == ($protected | length)) and + (($protected | map(.execution_boundary_id) | unique | length) == ($protected | length)) and + (($protected | map(.authority_ref.scope_sha256) | unique | length) == + ($protected | length)) and + (($other | map(.role) | unique | length) == ($other | length)); + +def profile_shape_ok: + schema::envelope_ok("profile") and + (.body | profile_body_shape_ok); + +def profile_relations_ok: + .body.bindings as $bindings | + protected_role_separation_ok($bindings); + +def profile_self_ok: + profile_shape_ok and profile_relations_ok; + +def tool_source_ok: + schema::exact_fields(["tool_id","package_source","config_source"];[]) and + (.tool_id | schema::id_ok) and + (.package_source | schema::source_value_ref_ok) and + (.config_source | schema::present_ok(schema::source_value_ref_ok)); + +def resolved_binding_ok: + schema::exact_fields( + ["binding","adapter_implementation","manifest_source","package_source", + "config_source","prompt_source","skill_sources","tool_sources"]; + []) and + (.binding | profile_binding_shape_ok) and + (.adapter_implementation | + schema::exact_fields(["id","version"];[]) and + (.id | schema::id_ok) and + (.version | schema::version_ok)) and + (.manifest_source | + schema::source_value_ref_ok and .value_format == "canonical-json") and + (.package_source | schema::source_value_ref_ok) and + (.config_source | schema::present_ok(schema::source_value_ref_ok)) and + (.prompt_source | schema::present_ok(schema::source_value_ref_ok)) and + (.skill_sources | + schema::bounded_set(0;32;schema::source_value_ref_ok;schema::source_git_key)) and + (.tool_sources | schema::bounded_set(0;32;tool_source_ok;.tool_id)); + +def resolved_profile_body_shape_ok: + schema::exact_fields( + ["profile_ref","profile_source","selection_ref","repository_context_ref", + "bindings"]; + []) and + (.profile_ref | schema::document_ref_kind_ok("profile")) and + (.profile_source | + schema::source_value_ref_ok and .value_format == "canonical-json") and + (.selection_ref | schema::scope_ref_purpose_ok("selection")) and + (.repository_context_ref | + schema::scope_ref_purpose_ok("repository-context")) and + (.bindings | + schema::bounded_set(4;8;resolved_binding_ok;.binding.binding_id)); + +def resolved_profile_body_ok: resolved_profile_body_shape_ok; + +def resolved_profile_shape_ok: + schema::envelope_ok("resolved_profile") and + (.body | resolved_profile_body_shape_ok); + +def present_source_values($present): + if $present.state == "present" then [$present.value] else [] end; + +def resolved_binding_source_claims($resolved_binding): + [$resolved_binding.manifest_source,$resolved_binding.package_source] + + present_source_values($resolved_binding.config_source) + + present_source_values($resolved_binding.prompt_source) + + $resolved_binding.skill_sources + + ($resolved_binding.tool_sources | + map([.package_source] + present_source_values(.config_source)) | + add // []); + +def resolved_profile_source_claims($body): + [$body.profile_source] + + ($body.bindings | map(resolved_binding_source_claims(.)) | add // []); + +def source_claims_agree($body): + resolved_profile_source_claims($body) | + group_by(schema::source_git_key) | + all(.[]; + (map(.value_format) | unique | length) == 1 and + (map(.value_sha256) | unique | length) == 1); + +def present_source_matches_optional_ref($source; $binding; $name): + ($source.state == "present") == ($binding | has($name)) and + (if $binding | has($name) then + $source.value.source == $binding[$name] + else true end); + +def tool_sources_match($binding; $resolved_binding): + ($resolved_binding.tool_sources | map(.tool_id)) == + ($binding.requested_tools | map(.tool_id)) and + all($resolved_binding.tool_sources[]; + . as $source | + [$binding.requested_tools[] | + select(.tool_id == $source.tool_id)] as $requested | + ($requested | length) == 1 and + $source.package_source.source == $requested[0].package_ref and + ($source.config_source.state == "present") == + ($requested[0].config_ref.state == "present") and + (if $source.config_source.state == "present" then + $source.config_source.value.source == $requested[0].config_ref.value + else true end)); + +def resolved_binding_projection_ok: + . as $resolved | + $resolved.binding as $binding | + $resolved.adapter_implementation.id == $binding.manifest_ref.id and + $resolved.manifest_source.value_sha256 == $binding.manifest_ref.sha256 and + $resolved.package_source.source == $binding.package_ref and + present_source_matches_optional_ref($resolved.config_source;$binding;"config_ref") and + present_source_matches_optional_ref($resolved.prompt_source;$binding;"prompt_ref") and + ($resolved.skill_sources | map(schema::source_git_key)) == + ($binding.skill_refs | map(schema::git_key)) and + tool_sources_match($binding;$resolved); + +def resolved_profile_relations_ok: + .body as $body | + ($body.bindings | map(.binding)) as $bindings | + $body.profile_source.value_sha256 == $body.profile_ref.sha256 and + protected_role_separation_ok($bindings) and + all($bindings[]; binding_capability_ok) and + all($body.bindings[]; resolved_binding_projection_ok) and + source_claims_agree($body); + +def resolved_profile_self_ok: + resolved_profile_shape_ok and resolved_profile_relations_ok; + +def document_pair_ok($kind): + schema::exact_fields(["content","sha256"];[]) and + (.content | schema::envelope_ok($kind)) and + (.sha256 | schema::sha256_ok); + +def document_ref_for_pair($pair): + { + schema_version:1, + kind:$pair.content.kind, + id:$pair.content.id, + sha256:$pair.sha256 + }; + +def profile_set_refs_ok($profile; $resolved; $manifests): + ($profile | document_pair_ok("profile")) and + ($resolved | document_pair_ok("resolved_profile")) and + ($manifests | type == "array") and + ($manifests | length) >= 1 and + ($manifests | length) <= 8 and + all($manifests[]; document_pair_ok("adapter_manifest")) and + (($manifests | map(.content.id) | unique | length) == ($manifests | length)) and + $resolved.content.body.profile_ref == document_ref_for_pair($profile); + +def manifest_for_ref($manifests; $ref): + [$manifests[] | select(document_ref_for_pair(.) == $ref)]; + +def offered_tool_ok($manifest; $tool): + $manifest.content.body.offered_tools | any(.[]; . == $tool); + +def binding_manifest_graph_ok($binding; $resolved_binding; $manifest): + ($resolved_binding.binding == $binding) and + ($manifest.content.body.offered_roles | index($binding.role) != null) and + ($manifest.content.body.offered_execution_kinds | + index($binding.execution_kind) != null) and + all($binding.requested_capabilities[]; . as $capability | + $manifest.content.body.offered_capabilities | index($capability) != null) and + all($binding.requested_permissions[]; . as $permission | + $manifest.content.body.offered_permissions | index($permission) != null) and + all($binding.requested_tools[]; . as $tool | + offered_tool_ok($manifest;$tool)) and + ($binding.package_ref == $manifest.content.body.package_ref) and + (if $binding | has("config_ref") then + $manifest.content.body | has("config_contract_ref") + else true end) and + ($resolved_binding.adapter_implementation.id == $manifest.content.id) and + ($resolved_binding.adapter_implementation.version == + $manifest.content.body.adapter_version) and + ($resolved_binding.manifest_source.value_sha256 == $manifest.sha256); + +def profile_set_graph_ok($profile; $resolved; $manifests): + $profile.content.body.bindings as $bindings | + $resolved.content.body.bindings as $resolved_bindings | + ($bindings | map(.binding_id)) == + ($resolved_bindings | map(.binding.binding_id)) and + ($resolved.content.body.profile_source.value_sha256 == $profile.sha256) and + ([ $bindings[].manifest_ref ] | unique) == + ([$manifests[] | document_ref_for_pair(.)] | unique) and + all($bindings[]; + . as $binding | + manifest_for_ref($manifests;$binding.manifest_ref) as $matches | + [$resolved_bindings[] | + select(.binding.binding_id == $binding.binding_id)] as $resolved_matches | + ($matches | length) == 1 and + ($resolved_matches | length) == 1 and + binding_manifest_graph_ok($binding;$resolved_matches[0];$matches[0])); + +def profile_set_ok($profile; $resolved; $manifests): + profile_set_refs_ok($profile;$resolved;$manifests) and + ($profile.content | profile_self_ok) and + ($resolved.content | resolved_profile_self_ok) and + all($manifests[].content; adapter_manifest_self_ok) and + profile_set_graph_ok($profile;$resolved;$manifests); + +def document_shape_ok: + if .kind == "adapter_manifest" then adapter_manifest_shape_ok + elif .kind == "profile" then profile_shape_ok + elif .kind == "resolved_profile" then resolved_profile_shape_ok + else false + end; + +def document_self_ok: + if .kind == "adapter_manifest" then adapter_manifest_self_ok + elif .kind == "profile" then profile_self_ok + elif .kind == "resolved_profile" then resolved_profile_self_ok + else false + end; diff --git a/scripts/test/portable-core-profile-graph-fixtures.jq b/scripts/test/portable-core-profile-graph-fixtures.jq new file mode 100644 index 0000000..6549400 --- /dev/null +++ b/scripts/test/portable-core-profile-graph-fixtures.jq @@ -0,0 +1,208 @@ +def metadata: + { + construction_base:"6ae9452848fd1bdec38aaef78efc842f5e938de3", + generation_id:"g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8da2c386", + parent_spec_blob:"c6511d96c1a5e6aed27ba2075b5add65c121f782", + schema_g3_comment:5466181650, + schema_merge_commit:"d48ecdb908a395c5205260a662db7d9d3f4c1eb4", + schema_export_oid:"fd3924d414a7d620c2bf5de919a45c2599d572ec", + ingress_g3_comment:5468279667, + ingress_export_oid:"e882b38b0106aac9142c667771f02e3107f8c52f", + registry_oid:"5e113105777694a280166e71d31efd19752e9562", + review_source_sha256:"31793a3ad42acf4df117ea158a78738e056bae550269483870487c3e146b27f9", + legacy_source_sha256:"3d5a6fb192f9bcaba5c4b89314d30f88a03b9d8a1e1e634297c267b14f096092", + profile_mapping_sha256:"7a1deb7cc114bb78f118eb74d9587cee0f7a9822025747cb87aaed0a11b562c4", + review_rows:7, + legacy_rows:92, + owned_rules:45, + direct_cases:135, + command_to_rule_cells:8, + forced_routes:8, + error_layer_cases:5, + guard_cases:16 + }; + +def sha($character): $character * 64; +def oid($character): $character * 40; + +def revision: + { + repository_id:"repo.example", + hash_algorithm:"sha1", + commit_id:(oid("1")) + }; + +def blob($path; $character): + { + revision:revision, + location:{kind:"path",value:$path}, + object_type:"blob", + object_id:(oid($character)), + mode:"100644" + }; + +def content($id; $digest): + {content_id:$id,media_type:"application/json",sha256:$digest}; + +def scope($purpose; $id; $digest): + { + purpose:$purpose, + decision_record_ref:content("decision-" + $id;$digest), + subject_ref:{type:"artifact",value:{type:"content",value:content($id;$digest)}}, + scope_sha256:$digest + }; + +def document_ref($kind; $id; $digest): + {schema_version:1,kind:$kind,id:$id,sha256:$digest}; + +def envelope($kind; $id; $body): + {schema_version:1,kind:$kind,id:$id,body:$body}; + +def absent: {state:"absent"}; +def present($value): {state:"present",value:$value}; + +def package_ref($role): + if $role == "producer" then blob("packages/producer.bin";"2") + elif $role == "publisher" then blob("packages/publisher.bin";"3") + elif $role == "reviewer" then blob("packages/reviewer.bin";"4") + else blob("packages/verifier.bin";"5") + end; + +def manifest_source_ref($role): + if $role == "producer" then blob("manifests/producer.json";"6") + elif $role == "publisher" then blob("manifests/publisher.json";"7") + elif $role == "reviewer" then blob("manifests/reviewer.json";"8") + else blob("manifests/verifier.json";"9") + end; + +def capability($role): + if $role == "producer" then "core.harness.produce.v1" + elif $role == "reviewer" then "core.review.change.v1" + elif $role == "verifier" then "core.verify.run.v1" + else null + end; + +def permissions($role; $execution): + if $role == "producer" and $execution == "model" then + ["core.perm.evidence.write.v1","core.perm.model.invoke.v1", + "core.perm.scratch.write.v1","core.perm.target.read.v1"] + elif $role == "reviewer" and $execution == "model" then + ["core.perm.evidence.write.v1","core.perm.model.invoke.v1", + "core.perm.target.read.v1"] + elif $role == "verifier" then + ["core.perm.candidate.execute.v1","core.perm.evidence.write.v1", + "core.perm.target.read.v1"] + else [] + end; + +def producer_tool: + { + tool_id:"tool.producer", + tool_version:"v1", + package_ref:blob("tools/producer.bin";"a"), + config_ref:present(blob("tools/producer.json";"b")) + }; + +def manifest_body($role): + (if $role == "producer" or $role == "reviewer" then "model" else "deterministic" end) as $execution | + { + adapter_version:"v1", + package_ref:package_ref($role), + offered_roles:[$role], + offered_execution_kinds:[$execution], + offered_capabilities:(if capability($role) == null then [] else [capability($role)] end), + offered_permissions:permissions($role;$execution), + offered_tools:(if $role == "producer" then [producer_tool] else [] end) + } + + (if $role == "producer" then + {config_contract_ref:scope("config-contract";"producer-config";sha("c"))} + else {} end); + +def manifest($role): + envelope("adapter_manifest";"manifest." + $role;manifest_body($role)); + +def manifest_docs: + [manifest("producer"),manifest("publisher"),manifest("reviewer"),manifest("verifier")]; + +def binding($role; $manifest_shas): + (if $role == "producer" or $role == "reviewer" then "model" else "deterministic" end) as $execution | + { + binding_id:("binding." + $role), + role:$role, + manifest_ref:document_ref("adapter_manifest";"manifest." + $role;$manifest_shas[$role]), + execution_kind:$execution, + adapter_instance_id:("instance." + $role), + principal_id:("principal." + $role), + execution_boundary_id:("boundary." + $role), + authority_ref:scope("authority";"authority-" + $role; + if $role == "producer" then sha("1") + elif $role == "publisher" then sha("2") + elif $role == "reviewer" then sha("3") + else sha("4") end), + package_ref:package_ref($role), + skill_refs:(if $role == "producer" then [blob("skills/producer.md";"c")] else [] end), + requested_tools:(if $role == "producer" then [producer_tool] else [] end), + requested_capabilities:(if capability($role) == null then [] else [capability($role)] end), + requested_permissions:permissions($role;$execution) + } + + (if $role == "producer" then + { + config_ref:blob("config/producer.json";"d"), + prompt_ref:blob("prompts/producer.md";"e"), + model_request:{provider_id:"provider.example",model_id:"model.example",effort_id:"high"} + } + elif $role == "reviewer" then + { + prompt_ref:blob("prompts/reviewer.md";"f"), + model_request:{provider_id:"provider.example",model_id:"review.example",effort_id:"high"} + } + else {} end); + +def profile_doc($manifest_shas): + envelope("profile";"profile.example"; + { + profile_version:"v1", + bindings:(["producer","publisher","reviewer","verifier"] | + map(binding(.;$manifest_shas))) + }); + +def source_value($source; $format; $digest): + {source:$source,value_format:$format,value_sha256:$digest}; + +def resolved_tool_source: + { + tool_id:"tool.producer", + package_source:source_value(producer_tool.package_ref;"raw-bytes";sha("a")), + config_source:present(source_value(producer_tool.config_ref.value;"raw-bytes";sha("b"))) + }; + +def resolved_binding($binding; $manifest_shas): + $binding.role as $role | + { + binding:$binding, + adapter_implementation:{id:("manifest." + $role),version:"v1"}, + manifest_source:source_value(manifest_source_ref($role);"canonical-json";$manifest_shas[$role]), + package_source:source_value($binding.package_ref;"raw-bytes"; + if $role == "producer" then sha("5") + elif $role == "publisher" then sha("6") + elif $role == "reviewer" then sha("7") + else sha("8") end), + config_source:(if $role == "producer" then + present(source_value($binding.config_ref;"raw-bytes";sha("9"))) else absent end), + prompt_source:(if $binding | has("prompt_ref") then + present(source_value($binding.prompt_ref;"raw-bytes"; + if $role == "producer" then sha("d") else sha("e") end)) else absent end), + skill_sources:(if $role == "producer" then + [source_value($binding.skill_refs[0];"raw-bytes";sha("f"))] else [] end), + tool_sources:(if $role == "producer" then [resolved_tool_source] else [] end) + }; + +def resolved_profile_doc($profile; $profile_sha; $manifest_shas): + envelope("resolved_profile";"resolved.example"; + { + profile_ref:document_ref("profile";$profile.id;$profile_sha), + profile_source:source_value(blob("profiles/profile.json";"0");"canonical-json";$profile_sha), + selection_ref:scope("selection";"selection.example";sha("a")), + repository_context_ref:scope("repository-context";"repository.example";sha("b")), + bindings:($profile.body.bindings | map(resolved_binding(.;$manifest_shas))) + }); diff --git a/scripts/test/portable-core-profile-graph-ledger.tsv b/scripts/test/portable-core-profile-graph-ledger.tsv new file mode 100644 index 0000000..85c65af --- /dev/null +++ b/scripts/test/portable-core-profile-graph-ledger.tsv @@ -0,0 +1,100 @@ +source row_id disposition rule_id test_id +review review-r0-f06 ported portable-core-profile-graph.binding-manifest-offer-closure portable-core-profile-graph.test.binding-outside-manifest-offer-rejected +review review-r0-f10 ported portable-core-profile-graph.resolved-source-document-digests portable-core-profile-graph.test.resolved-source-digest-mismatch-rejected +review review-r0-f11 ported portable-core-profile-graph.profile-set-exact-manifests portable-core-profile-graph.test.unreferenced-manifest-rejected +review review-r1-f04 ported portable-core-profile-graph.resolved-source-single-claim portable-core-profile-graph.test.conflicting-source-claim-rejected +review review-r1-f05 ported portable-core-profile-graph.dormant-binding-deterministic portable-core-profile-graph.test.dormant-model-binding-rejected +review review-r2-f01 ported portable-core-profile-graph.resolved-binding-invariants portable-core-profile-graph.test.resolved-binding-invariant-bypass-rejected +review review-r3-f01 ported portable-core-profile-graph.resolved-source-self-projections portable-core-profile-graph.test.resolved-source-projection-standalone-route +legacy legacy-test-001 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-001-validate-document-manifest-producer +legacy legacy-test-002 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-001-validate-document-manifest-producer +legacy legacy-test-003 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-003-validate-document-manifest-verifier +legacy legacy-test-004 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-003-validate-document-manifest-verifier +legacy legacy-test-005 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-005-validate-document-manifest-reviewer +legacy legacy-test-006 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-005-validate-document-manifest-reviewer +legacy legacy-test-007 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-007-validate-document-manifest-publisher +legacy legacy-test-008 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-007-validate-document-manifest-publisher +legacy legacy-test-009 ported portable-core-profile-graph.profile-body portable-core-profile-graph.test.legacy-009-validate-document-profile +legacy legacy-test-010 ported portable-core-profile-graph.profile-body portable-core-profile-graph.test.legacy-009-validate-document-profile +legacy legacy-test-011 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-011-validate-document-resolved-profile +legacy legacy-test-012 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-011-validate-document-resolved-profile +legacy legacy-test-017 ported portable-core-profile-graph.profile-set-graph portable-core-profile-graph.test.legacy-017-validate-profile-set +legacy legacy-test-018 ported portable-core-profile-graph.profile-set-graph portable-core-profile-graph.test.legacy-017-validate-profile-set +legacy legacy-test-037 ported portable-core-profile-graph.profile-set-exact-manifests portable-core-profile-graph.test.legacy-037-validate-profile-set-eight-manifests-boundary-4-extra-unreferenced-is-reject +legacy legacy-test-038 ported portable-core-profile-graph.profile-set-exact-manifests portable-core-profile-graph.test.legacy-037-validate-profile-set-eight-manifests-boundary-4-extra-unreferenced-is-reject +legacy legacy-test-083 ported portable-core-profile-graph.manifest-role-count portable-core-profile-graph.test.legacy-083-offered-roles-below-minimum +legacy legacy-test-084 ported portable-core-profile-graph.manifest-role-count portable-core-profile-graph.test.legacy-083-offered-roles-below-minimum +legacy legacy-test-085 ported portable-core-profile-graph.manifest-role-registry portable-core-profile-graph.test.legacy-085-offered-roles-unknown-enum +legacy legacy-test-086 ported portable-core-profile-graph.manifest-role-registry portable-core-profile-graph.test.legacy-085-offered-roles-unknown-enum +legacy legacy-test-087 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-087-unknown-top-level-field +legacy legacy-test-088 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-087-unknown-top-level-field +legacy legacy-test-089 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-089-missing-required-field +legacy legacy-test-090 ported portable-core-profile-graph.manifest-body portable-core-profile-graph.test.legacy-089-missing-required-field +legacy legacy-test-091 ported portable-core-profile-graph.manifest-tool-set portable-core-profile-graph.test.legacy-091-duplicate-tool-id-in-offered-tools +legacy legacy-test-092 ported portable-core-profile-graph.manifest-tool-set portable-core-profile-graph.test.legacy-091-duplicate-tool-id-in-offered-tools +legacy legacy-test-099 ported portable-core-profile-graph.profile-binding-count portable-core-profile-graph.test.legacy-099-below-4-binding-minimum +legacy legacy-test-100 ported portable-core-profile-graph.profile-binding-count portable-core-profile-graph.test.legacy-099-below-4-binding-minimum +legacy legacy-test-101 ported portable-core-profile-graph.binding-capability-closure portable-core-profile-graph.test.legacy-101-producer-requesting-verifiers-capability +legacy legacy-test-102 ported portable-core-profile-graph.binding-capability-closure portable-core-profile-graph.test.legacy-101-producer-requesting-verifiers-capability +legacy legacy-test-103 ported portable-core-profile-graph.protected-authority-required portable-core-profile-graph.test.legacy-103-protected-role-missing-authority-ref +legacy legacy-test-104 ported portable-core-profile-graph.protected-authority-required portable-core-profile-graph.test.legacy-103-protected-role-missing-authority-ref +legacy legacy-test-105 ported portable-core-profile-graph.protected-authority-distinct portable-core-profile-graph.test.legacy-105-two-protected-roles-share-one-authority-scope +legacy legacy-test-106 ported portable-core-profile-graph.protected-authority-distinct portable-core-profile-graph.test.legacy-105-two-protected-roles-share-one-authority-scope +legacy legacy-test-107 ported portable-core-profile-graph.protected-principal-distinct portable-core-profile-graph.test.legacy-107-two-protected-roles-share-one-principal-id +legacy legacy-test-108 ported portable-core-profile-graph.protected-principal-distinct portable-core-profile-graph.test.legacy-107-two-protected-roles-share-one-principal-id +legacy legacy-test-109 ported portable-core-profile-graph.binding-execution-kind portable-core-profile-graph.test.legacy-109-verifier-forced-to-model-execution +legacy legacy-test-110 ported portable-core-profile-graph.binding-execution-kind portable-core-profile-graph.test.legacy-109-verifier-forced-to-model-execution +legacy legacy-test-111 ported portable-core-profile-graph.deterministic-skill-empty portable-core-profile-graph.test.legacy-111-deterministic-binding-with-non-empty-skill-refs +legacy legacy-test-112 ported portable-core-profile-graph.deterministic-skill-empty portable-core-profile-graph.test.legacy-111-deterministic-binding-with-non-empty-skill-refs +legacy legacy-test-113 ported portable-core-profile-graph.binding-id-unique portable-core-profile-graph.test.legacy-113-duplicate-binding-id +legacy legacy-test-114 ported portable-core-profile-graph.binding-id-unique portable-core-profile-graph.test.legacy-113-duplicate-binding-id +legacy legacy-test-115 ported portable-core-profile-graph.binding-id-order portable-core-profile-graph.test.legacy-115-bindings-not-in-canonical-binding-id-sorted-order +legacy legacy-test-116 ported portable-core-profile-graph.binding-id-order portable-core-profile-graph.test.legacy-115-bindings-not-in-canonical-binding-id-sorted-order +legacy legacy-test-117 ported portable-core-profile-graph.binding-execution-kind portable-core-profile-graph.test.legacy-117-producer-allowed-to-use-model-execution +legacy legacy-test-118 ported portable-core-profile-graph.binding-execution-kind portable-core-profile-graph.test.legacy-117-producer-allowed-to-use-model-execution +legacy legacy-test-119 ported portable-core-profile-graph.binding-execution-kind portable-core-profile-graph.test.legacy-119-dormant-role-publisher-forced-to-model-execution +legacy legacy-test-120 ported portable-core-profile-graph.binding-execution-kind portable-core-profile-graph.test.legacy-119-dormant-role-publisher-forced-to-model-execution +legacy legacy-test-121 ported portable-core-profile-graph.resolved-profile-body portable-core-profile-graph.test.legacy-121-missing-selection-ref +legacy legacy-test-122 ported portable-core-profile-graph.resolved-profile-body portable-core-profile-graph.test.legacy-121-missing-selection-ref +legacy legacy-test-123 ported portable-core-profile-graph.resolved-profile-body portable-core-profile-graph.test.legacy-123-selection-ref-carries-the-wrong-purpose +legacy legacy-test-124 ported portable-core-profile-graph.resolved-profile-body portable-core-profile-graph.test.legacy-123-selection-ref-carries-the-wrong-purpose +legacy legacy-test-125 ported portable-core-profile-graph.resolved-binding-count portable-core-profile-graph.test.legacy-125-resolved-bindings-below-the-4-minimum +legacy legacy-test-126 ported portable-core-profile-graph.resolved-binding-count portable-core-profile-graph.test.legacy-125-resolved-bindings-below-the-4-minimum +legacy legacy-test-129 ported portable-core-profile-graph.resolved-tool-source-set portable-core-profile-graph.test.legacy-129-duplicate-tool-id-in-tool-sources +legacy legacy-test-130 ported portable-core-profile-graph.resolved-tool-source-set portable-core-profile-graph.test.legacy-129-duplicate-tool-id-in-tool-sources +legacy legacy-test-183 ported portable-core-profile-graph.resolved-package-source portable-core-profile-graph.test.legacy-183-resolved-package-source-does-not-match-the-bindings-package-ref +legacy legacy-test-184 ported portable-core-profile-graph.resolved-package-source portable-core-profile-graph.test.legacy-183-resolved-package-source-does-not-match-the-bindings-package-ref +legacy legacy-test-185 ported portable-core-profile-graph.profile-set-manifest-version portable-core-profile-graph.test.legacy-185-resolved-adapter-implementation-version-does-not-match-the-manifest +legacy legacy-test-186 ported portable-core-profile-graph.profile-set-manifest-version portable-core-profile-graph.test.legacy-185-resolved-adapter-implementation-version-does-not-match-the-manifest +legacy legacy-test-187 ported portable-core-profile-graph.profile-set-binding-identity portable-core-profile-graph.test.legacy-187-resolved-bindings-do-not-cover-the-same-binding-id-set-as-the-profile +legacy legacy-test-188 ported portable-core-profile-graph.profile-set-binding-identity portable-core-profile-graph.test.legacy-187-resolved-bindings-do-not-cover-the-same-binding-id-set-as-the-profile +legacy legacy-test-189 ported portable-core-profile-graph.profile-set-profile-ref portable-core-profile-graph.test.legacy-189-mutated-profiles-own-digest-no-longer-matches-the-resolved-profiles-profile +legacy legacy-test-190 ported portable-core-profile-graph.profile-set-profile-ref portable-core-profile-graph.test.legacy-189-mutated-profiles-own-digest-no-longer-matches-the-resolved-profiles-profile +legacy legacy-test-191 ported portable-core-profile-graph.profile-set-exact-manifests portable-core-profile-graph.test.legacy-191-a-referenced-manifest-is-simply-not-supplied-profile-resolved-digests-untouc +legacy legacy-test-192 ported portable-core-profile-graph.profile-set-exact-manifests portable-core-profile-graph.test.legacy-191-a-referenced-manifest-is-simply-not-supplied-profile-resolved-digests-untouc +legacy legacy-test-193 ported portable-core-profile-graph.profile-set-profile-ref portable-core-profile-graph.test.legacy-193-resolved-profile-ref-digest-does-not-match-the-supplied-profile +legacy legacy-test-194 ported portable-core-profile-graph.profile-set-profile-ref portable-core-profile-graph.test.legacy-193-resolved-profile-ref-digest-does-not-match-the-supplied-profile +legacy legacy-test-195 ported portable-core-profile-graph.profile-set-manifest-id-unique portable-core-profile-graph.test.legacy-195-two-supplied-manifests-share-one-document-id +legacy legacy-test-196 ported portable-core-profile-graph.profile-set-manifest-id-unique portable-core-profile-graph.test.legacy-195-two-supplied-manifests-share-one-document-id +legacy legacy-test-197 ported portable-core-profile-graph.profile-set-config-source portable-core-profile-graph.test.legacy-197-resolved-config-source-present-without-a-config-ref-on-the-binding +legacy legacy-test-198 ported portable-core-profile-graph.profile-set-config-source portable-core-profile-graph.test.legacy-197-resolved-config-source-present-without-a-config-ref-on-the-binding +legacy legacy-test-199 ported portable-core-profile-graph.profile-set-source-digests portable-core-profile-graph.test.legacy-199-resolved-profile-source-digest-does-not-match-the-supplied-profiles-real-byt +legacy legacy-test-200 ported portable-core-profile-graph.profile-set-source-digests portable-core-profile-graph.test.legacy-199-resolved-profile-source-digest-does-not-match-the-supplied-profiles-real-byt +legacy legacy-test-201 ported portable-core-profile-graph.profile-set-source-digests portable-core-profile-graph.test.legacy-201-resolved-manifest-source-digest-does-not-match-the-supplied-manifests-real-b +legacy legacy-test-202 ported portable-core-profile-graph.profile-set-source-digests portable-core-profile-graph.test.legacy-201-resolved-manifest-source-digest-does-not-match-the-supplied-manifests-real-b +legacy legacy-test-203 ported portable-core-profile-graph.profile-set-role-offer portable-core-profile-graph.test.legacy-203-manifest-does-not-offer-the-bindings-role +legacy legacy-test-204 ported portable-core-profile-graph.profile-set-role-offer portable-core-profile-graph.test.legacy-203-manifest-does-not-offer-the-bindings-role +legacy legacy-test-205 ported portable-core-profile-graph.profile-set-capability-offer portable-core-profile-graph.test.legacy-205-manifest-does-not-offer-the-bindings-requested-capability +legacy legacy-test-206 ported portable-core-profile-graph.profile-set-capability-offer portable-core-profile-graph.test.legacy-205-manifest-does-not-offer-the-bindings-requested-capability +legacy legacy-test-207 ported portable-core-profile-graph.source-claim-unique portable-core-profile-graph.test.legacy-207-two-bindings-claim-different-digests-for-the-same-source-git-object +legacy legacy-test-208 ported portable-core-profile-graph.source-claim-unique portable-core-profile-graph.test.legacy-207-two-bindings-claim-different-digests-for-the-same-source-git-object +legacy legacy-test-257 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-257-embedded-producer-binding-with-emptied-capability-closure +legacy legacy-test-258 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-257-embedded-producer-binding-with-emptied-capability-closure +legacy legacy-test-259 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-259-two-embedded-protected-role-bindings-share-one-principal-id +legacy legacy-test-260 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-259-two-embedded-protected-role-bindings-share-one-principal-id +legacy legacy-test-261 replaced-by portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-261-resolved-profile-self-forced-route-document +legacy legacy-test-262 replaced-by portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-261-resolved-profile-self-forced-route-document +legacy legacy-test-263 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-263-stage-run-approves-an-operation-its-embedded-binding-never-requested +legacy legacy-test-264 ported portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-263-stage-run-approves-an-operation-its-embedded-binding-never-requested +legacy legacy-test-265 replaced-by portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-265-resolved-profile-self-forced-route-stage-run +legacy legacy-test-266 replaced-by portable-core-profile-graph.resolved-profile-self portable-core-profile-graph.test.legacy-265-resolved-profile-self-forced-route-stage-run diff --git a/scripts/test/portable-core-profile-graph.test.sh b/scripts/test/portable-core-profile-graph.test.sh new file mode 100755 index 0000000..e4ad063 --- /dev/null +++ b/scripts/test/portable-core-profile-graph.test.sh @@ -0,0 +1,1053 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2016 +set -euo pipefail + +profile_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" +profile_generation="g-14b7ad8ce54c3b8c585ff92063d71551ffc7394cc2294d0297bc7d2b8da2c386" +profile_base="6ae9452848fd1bdec38aaef78efc842f5e938de3" +profile_parent_spec="c6511d96c1a5e6aed27ba2075b5add65c121f782" +profile_schema_merge="d48ecdb908a395c5205260a662db7d9d3f4c1eb4" +profile_schema_oid="fd3924d414a7d620c2bf5de919a45c2599d572ec" +profile_ingress_oid="e882b38b0106aac9142c667771f02e3107f8c52f" +profile_registry_oid="5e113105777694a280166e71d31efd19752e9562" +profile_schema_path="core/v1/generations/$profile_generation/modules/schema.jq" +profile_ingress_path="core/v1/generations/$profile_generation/core-ingress.sh" +profile_registry_path="core/v1/generation-registry.json" +profile_module_dir="$profile_root/core/v1/generations/$profile_generation/modules" +profile_module="$profile_module_dir/profile_graph.jq" +profile_fixture_dir="$profile_root/scripts/test" +profile_ledger="$profile_fixture_dir/portable-core-profile-graph-ledger.tsv" +profile_manifest="$profile_root/ci/required-files.txt" +profile_tmp="$(mktemp -d "${TMPDIR:-/tmp}/ystack-portable-profile.XXXXXX")" +profile_download="" + +cleanup() { + if [ -n "$profile_download" ] && [ -f "$profile_download" ]; then + rm -f -- "$profile_download" + fi + rm -rf -- "$profile_tmp" +} +trap cleanup EXIT + +sha256_path() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + +profile_platform="$(uname -s):$(uname -m)" +case "$profile_platform" in + Linux:x86_64) + profile_asset="jq-linux64" + profile_asset_sha256="af986793a515d500ab2d35f8d2aecd656e764504b789b66d7e1a0b727a124c44" + ;; + Darwin:x86_64|Darwin:arm64) + profile_asset="jq-osx-amd64" + profile_asset_sha256="5c0a0a3ea600f302ee458b30317425dd9632d1ad8882259fcaf4e9b868b2b1ef" + ;; + *) + echo "FAIL: unsupported jq 1.6 proof platform: $profile_platform" >&2 + exit 1 + ;; +esac + +profile_cache="${TMPDIR:-/tmp}/ystack-portable-core-jq16" +mkdir -p "$profile_cache" +profile_jq="$profile_cache/$profile_asset" +if [ ! -f "$profile_jq" ] || + [ "$(sha256_path "$profile_jq")" != "$profile_asset_sha256" ]; then + profile_download="$(mktemp "$profile_cache/.jq-1.6.XXXXXX")" + curl --proto '=https' --tlsv1.2 -fsSL \ + "https://github.com/jqlang/jq/releases/download/jq-1.6/$profile_asset" \ + -o "$profile_download" + if [ "$(sha256_path "$profile_download")" != "$profile_asset_sha256" ]; then + echo "FAIL: jq 1.6 release asset digest mismatch" >&2 + exit 1 + fi + chmod 0555 "$profile_download" + mv "$profile_download" "$profile_jq" + profile_download="" +fi + +profile_jq_command=("$profile_jq") +if [ "$profile_platform" = "Darwin:arm64" ]; then + profile_jq_command=(/usr/bin/arch -x86_64 "$profile_jq") +fi + +if [ "$(sha256_path "$profile_jq")" != "$profile_asset_sha256" ] || + [ "$("${profile_jq_command[@]}" --version)" != "jq-1.6" ]; then + echo "FAIL: pinned jq 1.6 identity check failed" >&2 + exit 1 +fi + +fixture_value() { + local expression="$1" + "${profile_jq_command[@]}" -L "$profile_fixture_dir" -S -c -n \ + "import \"portable-core-profile-graph-fixtures\" as fixture; $expression" +} + +fixture_metadata="$(fixture_value 'fixture::metadata')" + +roles=(producer publisher reviewer verifier) +manifest_files=() +manifest_shas=() +for index in 0 1 2 3; do + manifest_file="$profile_tmp/manifest-${roles[$index]}.json" + fixture_value "fixture::manifest_docs[$index]" > "$manifest_file" + manifest_files+=("$manifest_file") + manifest_shas+=("$(sha256_path "$manifest_file")") +done + +manifest_sha_map="$("${profile_jq_command[@]}" -n \ + --arg producer "${manifest_shas[0]}" \ + --arg publisher "${manifest_shas[1]}" \ + --arg reviewer "${manifest_shas[2]}" \ + --arg verifier "${manifest_shas[3]}" \ + '{producer:$producer,publisher:$publisher,reviewer:$reviewer,verifier:$verifier}')" + +profile_file="$profile_tmp/profile.json" +"${profile_jq_command[@]}" -L "$profile_fixture_dir" -S -c -n \ + --argjson manifest_shas "$manifest_sha_map" \ + 'import "portable-core-profile-graph-fixtures" as fixture; + fixture::profile_doc($manifest_shas)' > "$profile_file" +profile_sha="$(sha256_path "$profile_file")" + +resolved_file="$profile_tmp/resolved-profile.json" +"${profile_jq_command[@]}" -L "$profile_fixture_dir" -S -c -n \ + --slurpfile profile "$profile_file" \ + --arg profile_sha "$profile_sha" \ + --argjson manifest_shas "$manifest_sha_map" \ + 'import "portable-core-profile-graph-fixtures" as fixture; + fixture::resolved_profile_doc($profile[0];$profile_sha;$manifest_shas)' > "$resolved_file" +resolved_sha="$(sha256_path "$resolved_file")" + +profile_graph="$profile_tmp/graph.json" +"${profile_jq_command[@]}" -S -c -n \ + --slurpfile profile "$profile_file" \ + --slurpfile resolved "$resolved_file" \ + --slurpfile m0 "${manifest_files[0]}" \ + --slurpfile m1 "${manifest_files[1]}" \ + --slurpfile m2 "${manifest_files[2]}" \ + --slurpfile m3 "${manifest_files[3]}" \ + --arg profile_sha "$profile_sha" \ + --arg resolved_sha "$resolved_sha" \ + --arg m0_sha "${manifest_shas[0]}" \ + --arg m1_sha "${manifest_shas[1]}" \ + --arg m2_sha "${manifest_shas[2]}" \ + --arg m3_sha "${manifest_shas[3]}" \ + '{profile:{content:$profile[0],sha256:$profile_sha}, + resolved:{content:$resolved[0],sha256:$resolved_sha}, + manifests:[ + {content:$m0[0],sha256:$m0_sha}, + {content:$m1[0],sha256:$m1_sha}, + {content:$m2[0],sha256:$m2_sha}, + {content:$m3[0],sha256:$m3_sha}]}' > "$profile_graph" + +build_graph_with_producer_manifest() { + local output_file="$1" + local mutation="$2" + local output_name="${output_file##*/}" + local variant_manifest="$profile_tmp/$output_name.manifest.json" + local variant_profile="$profile_tmp/$output_name.profile.json" + local variant_resolved="$profile_tmp/$output_name.resolved.json" + local variant_manifest_sha + local variant_profile_sha + local variant_resolved_sha + local variant_sha_map + + "${profile_jq_command[@]}" -S -c "$mutation" "${manifest_files[0]}" > \ + "$variant_manifest" + variant_manifest_sha="$(sha256_path "$variant_manifest")" + variant_sha_map="$("${profile_jq_command[@]}" -n \ + --arg producer "$variant_manifest_sha" \ + --arg publisher "${manifest_shas[1]}" \ + --arg reviewer "${manifest_shas[2]}" \ + --arg verifier "${manifest_shas[3]}" \ + '{producer:$producer,publisher:$publisher,reviewer:$reviewer,verifier:$verifier}')" + "${profile_jq_command[@]}" -L "$profile_fixture_dir" -S -c -n \ + --argjson manifest_shas "$variant_sha_map" \ + 'import "portable-core-profile-graph-fixtures" as fixture; + fixture::profile_doc($manifest_shas)' > "$variant_profile" + variant_profile_sha="$(sha256_path "$variant_profile")" + "${profile_jq_command[@]}" -L "$profile_fixture_dir" -S -c -n \ + --slurpfile profile "$variant_profile" \ + --arg profile_sha "$variant_profile_sha" \ + --argjson manifest_shas "$variant_sha_map" \ + 'import "portable-core-profile-graph-fixtures" as fixture; + fixture::resolved_profile_doc($profile[0];$profile_sha;$manifest_shas)' > \ + "$variant_resolved" + variant_resolved_sha="$(sha256_path "$variant_resolved")" + "${profile_jq_command[@]}" -S -c -n \ + --slurpfile profile "$variant_profile" \ + --slurpfile resolved "$variant_resolved" \ + --slurpfile m0 "$variant_manifest" \ + --slurpfile m1 "${manifest_files[1]}" \ + --slurpfile m2 "${manifest_files[2]}" \ + --slurpfile m3 "${manifest_files[3]}" \ + --arg profile_sha "$variant_profile_sha" \ + --arg resolved_sha "$variant_resolved_sha" \ + --arg m0_sha "$variant_manifest_sha" \ + --arg m1_sha "${manifest_shas[1]}" \ + --arg m2_sha "${manifest_shas[2]}" \ + --arg m3_sha "${manifest_shas[3]}" \ + '{profile:{content:$profile[0],sha256:$profile_sha}, + resolved:{content:$resolved[0],sha256:$resolved_sha}, + manifests:[ + {content:$m0[0],sha256:$m0_sha}, + {content:$m1[0],sha256:$m1_sha}, + {content:$m2[0],sha256:$m2_sha}, + {content:$m3[0],sha256:$m3_sha}]}' > "$output_file" +} + +profile_role_offer_graph="$profile_tmp/role-offer-graph.json" +profile_capability_offer_graph="$profile_tmp/capability-offer-graph.json" +profile_execution_offer_graph="$profile_tmp/execution-offer-graph.json" +profile_permission_offer_graph="$profile_tmp/permission-offer-graph.json" +profile_tool_version_graph="$profile_tmp/tool-version-graph.json" +profile_tool_package_graph="$profile_tmp/tool-package-graph.json" +profile_tool_config_presence_graph="$profile_tmp/tool-config-presence-graph.json" +profile_tool_config_object_graph="$profile_tmp/tool-config-object-graph.json" +profile_package_graph="$profile_tmp/package-graph.json" +profile_config_contract_graph="$profile_tmp/config-contract-graph.json" +profile_superset_offer_graph="$profile_tmp/superset-offer-graph.json" +build_graph_with_producer_manifest "$profile_role_offer_graph" \ + '.body.offered_roles=["reviewer"]' +build_graph_with_producer_manifest "$profile_capability_offer_graph" \ + '.body.offered_capabilities=[]' +build_graph_with_producer_manifest "$profile_execution_offer_graph" \ + '.body.offered_execution_kinds=["deterministic"]' +build_graph_with_producer_manifest "$profile_permission_offer_graph" \ + '.body.offered_permissions=[]' +build_graph_with_producer_manifest "$profile_tool_version_graph" \ + '.body.offered_tools[0].tool_version="v2"' +build_graph_with_producer_manifest "$profile_tool_package_graph" \ + '.body.offered_tools[0].package_ref.object_id=("0"*40)' +build_graph_with_producer_manifest "$profile_tool_config_presence_graph" \ + '.body.offered_tools[0].config_ref={state:"absent"}' +build_graph_with_producer_manifest "$profile_tool_config_object_graph" \ + '.body.offered_tools[0].config_ref.value.object_id=("0"*40)' +build_graph_with_producer_manifest "$profile_package_graph" \ + '.body.package_ref.object_id=("0"*40)' +build_graph_with_producer_manifest "$profile_config_contract_graph" \ + 'del(.body.config_contract_ref)' +build_graph_with_producer_manifest "$profile_superset_offer_graph" \ + '.body.offered_roles=["producer","reviewer"] | + .body.offered_execution_kinds=["deterministic","model"] | + .body.offered_capabilities=["core.harness.produce.v1","core.review.change.v1","core.verify.run.v1"] | + .body.offered_permissions=["core.perm.candidate.execute.v1","core.perm.evidence.write.v1","core.perm.model.invoke.v1","core.perm.scratch.write.v1","core.perm.target.read.v1"] | + .body.offered_tools += [(.body.offered_tools[0] | .tool_id="tool.zextra")]' + +profile_extra_graph="$profile_tmp/extra-manifest-graph.json" +cp "$profile_graph" "$profile_extra_graph" +for extra_index in 1 2 3 4; do + extra_manifest="$profile_tmp/extra-manifest-$extra_index.json" + extra_graph_next="$profile_tmp/extra-manifest-graph-$extra_index.json" + "${profile_jq_command[@]}" -S -c \ + --arg id "manifest.extra$extra_index" \ + '.id=$id' "${manifest_files[0]}" > "$extra_manifest" + extra_sha="$(sha256_path "$extra_manifest")" + "${profile_jq_command[@]}" -S -c \ + --slurpfile extra "$extra_manifest" \ + --arg sha "$extra_sha" \ + '.manifests += [{content:$extra[0],sha256:$sha}]' \ + "$profile_extra_graph" > "$extra_graph_next" + mv "$extra_graph_next" "$profile_extra_graph" +done + +profile_failures=0 +profile_direct_total=0 +profile_direct_passed=0 +profile_cell_total=0 +profile_cell_passed=0 +profile_forced_total=0 +profile_forced_passed=0 +profile_layer_total=0 +profile_layer_passed=0 +profile_guard_total=0 +profile_guard_passed=0 +profile_seen_rules="$profile_tmp/seen-rules" +profile_seen_tests="$profile_tmp/seen-tests" +: > "$profile_seen_rules" +: > "$profile_seen_tests" + +fail_case() { + echo "FAIL: $1" >&2 + profile_failures=$((profile_failures + 1)) +} + +mark_rule() { + printf '%s\n' "$1" >> "$profile_seen_rules" +} + +mark_test() { + printf '%s\n' "$1" >> "$profile_seen_tests" +} + +expect_expression() { + local case_id="$1" + local expected="$2" + local expression="$3" + local graph_file="${4:-$profile_graph}" + local actual + profile_direct_total=$((profile_direct_total + 1)) + if ! actual="$("${profile_jq_command[@]}" -L "$profile_module_dir" \ + --slurpfile graph "$graph_file" -n \ + 'import "profile_graph" as profile_graph; + $graph[0] as $g | ('"$expression"')')"; then + fail_case "$case_id raised a jq error" + return + fi + if [ "$actual" = "$expected" ]; then + profile_direct_passed=$((profile_direct_passed + 1)) + else + fail_case "$case_id expected $expected, got $actual" + fi +} + +expect_true() { + expect_expression "$1" true "$2" "${3:-$profile_graph}" +} + +expect_false() { + expect_expression "$1" false "$2" "${3:-$profile_graph}" +} + +expect_layer() { + local case_id="$1" + local expected="$2" + local expression="$3" + local actual + profile_layer_total=$((profile_layer_total + 1)) + if ! actual="$("${profile_jq_command[@]}" -r -L "$profile_module_dir" \ + --slurpfile graph "$profile_graph" -n \ + 'import "profile_graph" as profile_graph; + $graph[0] as $g | ('"$expression"')')"; then + fail_case "$case_id raised a jq error" + return + fi + if [ "$actual" = "$expected" ]; then + profile_layer_passed=$((profile_layer_passed + 1)) + else + fail_case "$case_id expected $expected, got $actual" + fi +} + +mapped_true() { + mark_test "$1" + expect_true "$2" "$3" "${4:-$profile_graph}" +} + +mapped_false() { + mark_test "$1" + expect_false "$2" "$3" "${4:-$profile_graph}" +} + +set_ok='profile_graph::profile_set_ok($g.profile;$g.resolved;$g.manifests)' + +mapped_true portable-core-profile-graph.test.legacy-001-validate-document-manifest-producer \ + manifest-producer '$g.manifests[0].content | profile_graph::adapter_manifest_self_ok' +mapped_true portable-core-profile-graph.test.legacy-003-validate-document-manifest-verifier \ + manifest-verifier '$g.manifests[3].content | profile_graph::adapter_manifest_self_ok' +mapped_true portable-core-profile-graph.test.legacy-005-validate-document-manifest-reviewer \ + manifest-reviewer '$g.manifests[2].content | profile_graph::adapter_manifest_self_ok' +mapped_true portable-core-profile-graph.test.legacy-007-validate-document-manifest-publisher \ + manifest-publisher '$g.manifests[1].content | profile_graph::adapter_manifest_self_ok' +mark_rule portable-core-profile-graph.manifest-body + +mapped_true portable-core-profile-graph.test.legacy-009-validate-document-profile \ + profile-valid '$g.profile.content | profile_graph::profile_self_ok' +mark_rule portable-core-profile-graph.profile-body +mapped_true portable-core-profile-graph.test.legacy-011-validate-document-resolved-profile \ + resolved-valid '$g.resolved.content | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.resolved-profile-self +mapped_true portable-core-profile-graph.test.legacy-017-validate-profile-set \ + graph-valid "$set_ok" +mark_rule portable-core-profile-graph.profile-set-graph + +mapped_false portable-core-profile-graph.test.legacy-037-validate-profile-set-eight-manifests-boundary-4-extra-unreferenced-is-reject \ + graph-extra-manifests \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_extra_graph" +mark_rule portable-core-profile-graph.profile-set-exact-manifests + +mapped_false portable-core-profile-graph.test.legacy-083-offered-roles-below-minimum \ + manifest-role-min \ + '$g.manifests[0].content | .body.offered_roles=[] | profile_graph::adapter_manifest_shape_ok' +mark_rule portable-core-profile-graph.manifest-role-count +mapped_false portable-core-profile-graph.test.legacy-085-offered-roles-unknown-enum \ + manifest-role-enum \ + '$g.manifests[0].content | .body.offered_roles=["unknown"] | profile_graph::adapter_manifest_shape_ok' +mark_rule portable-core-profile-graph.manifest-role-registry +mapped_false portable-core-profile-graph.test.legacy-087-unknown-top-level-field \ + manifest-extra-field \ + '$g.manifests[0].content | .body.extra=true | profile_graph::adapter_manifest_shape_ok' +mapped_false portable-core-profile-graph.test.legacy-089-missing-required-field \ + manifest-missing-field \ + '$g.manifests[0].content | del(.body.adapter_version) | profile_graph::adapter_manifest_shape_ok' +mapped_false portable-core-profile-graph.test.legacy-091-duplicate-tool-id-in-offered-tools \ + manifest-duplicate-tool \ + '$g.manifests[0].content | .body.offered_tools += [.body.offered_tools[0]] | profile_graph::adapter_manifest_shape_ok' +mark_rule portable-core-profile-graph.manifest-tool-set + +mapped_false portable-core-profile-graph.test.legacy-099-below-4-binding-minimum \ + profile-binding-min \ + '$g.profile.content | .body.bindings=(.body.bindings[0:3]) | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.profile-binding-count +mapped_false portable-core-profile-graph.test.legacy-101-producer-requesting-verifiers-capability \ + profile-capability-closure \ + '$g.profile.content | .body.bindings |= map(if .role=="producer" then .requested_capabilities=["core.verify.run.v1"] else . end) | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.binding-capability-closure +expect_false profile-permissions-missing \ + '$g.profile.content | .body.bindings |= map(if .role=="producer" then .requested_permissions=["core.perm.evidence.write.v1","core.perm.model.invoke.v1","core.perm.scratch.write.v1"] else . end) | profile_graph::profile_shape_ok' +expect_false profile-permissions-extra \ + '$g.profile.content | .body.bindings |= map(if .role=="producer" then .requested_permissions=["core.perm.candidate.execute.v1","core.perm.evidence.write.v1","core.perm.model.invoke.v1","core.perm.scratch.write.v1","core.perm.target.read.v1"] else . end) | profile_graph::profile_shape_ok' +mapped_false portable-core-profile-graph.test.legacy-103-protected-role-missing-authority-ref \ + protected-authority-required \ + '$g.profile.content | .body.bindings |= map(if .role=="publisher" then del(.authority_ref) else . end) | profile_graph::profile_self_ok' +mark_rule portable-core-profile-graph.protected-authority-required +mapped_false portable-core-profile-graph.test.legacy-105-two-protected-roles-share-one-authority-scope \ + protected-authority-distinct \ + '$g.profile.content | (.body.bindings[]|select(.role=="producer")|.authority_ref.scope_sha256) as $shared | .body.bindings |= map(if .role=="reviewer" then .authority_ref.scope_sha256=$shared else . end) | profile_graph::profile_self_ok' +mark_rule portable-core-profile-graph.protected-authority-distinct +mapped_false portable-core-profile-graph.test.legacy-107-two-protected-roles-share-one-principal-id \ + protected-principal-distinct \ + '$g.profile.content | (.body.bindings[]|select(.role=="producer")|.principal_id) as $shared | .body.bindings |= map(if .role=="reviewer" then .principal_id=$shared else . end) | profile_graph::profile_self_ok' +mark_rule portable-core-profile-graph.protected-principal-distinct +mapped_false portable-core-profile-graph.test.legacy-109-verifier-forced-to-model-execution \ + verifier-model \ + '$g.profile.content | .body.bindings |= map(if .role=="verifier" then .execution_kind="model" | .model_request={provider_id:"p",model_id:"m",effort_id:"e"} | .prompt_ref=.package_ref else . end) | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.binding-execution-kind +mapped_false portable-core-profile-graph.test.legacy-111-deterministic-binding-with-non-empty-skill-refs \ + deterministic-skills \ + '$g.profile.content | .body.bindings |= map(if .role=="publisher" then .skill_refs=[.package_ref] else . end) | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.deterministic-skill-empty +mapped_false portable-core-profile-graph.test.legacy-113-duplicate-binding-id \ + binding-id-duplicate \ + '$g.profile.content | .body.bindings[1].binding_id=.body.bindings[0].binding_id | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.binding-id-unique +mapped_false portable-core-profile-graph.test.legacy-115-bindings-not-in-canonical-binding-id-sorted-order \ + binding-id-order \ + '$g.profile.content | .body.bindings|=reverse | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.binding-id-order +mapped_true portable-core-profile-graph.test.legacy-117-producer-allowed-to-use-model-execution \ + producer-model \ + '$g.profile.content | .body.bindings[] | select(.role=="producer") | profile_graph::profile_binding_ok' +expect_true producer-deterministic \ + '$g.profile.content.body.bindings[] | select(.role=="producer") | .execution_kind="deterministic" | del(.model_request,.prompt_ref) | .skill_refs=[] | .requested_permissions=["core.perm.evidence.write.v1","core.perm.scratch.write.v1","core.perm.target.read.v1"] | profile_graph::profile_binding_ok' +expect_true reviewer-deterministic \ + '$g.profile.content.body.bindings[] | select(.role=="reviewer") | .execution_kind="deterministic" | del(.model_request,.prompt_ref) | .skill_refs=[] | .requested_permissions=["core.perm.evidence.write.v1","core.perm.target.read.v1"] | profile_graph::profile_binding_ok' +mapped_false portable-core-profile-graph.test.legacy-119-dormant-role-publisher-forced-to-model-execution \ + publisher-model \ + '$g.profile.content | .body.bindings |= map(if .role=="publisher" then .execution_kind="model" | .model_request={provider_id:"p",model_id:"m",effort_id:"e"} | .prompt_ref=.package_ref else . end) | profile_graph::profile_shape_ok' +for dormant_role in publisher forge ci execution identity; do + expect_false "dormant-$dormant_role-model" \ + '$g.profile.content.body.bindings[1] | .role="'"$dormant_role"'" | .execution_kind="model" | .model_request={provider_id:"p",model_id:"m",effort_id:"e"} | .prompt_ref=.package_ref | profile_graph::profile_binding_ok' +done +expect_true optional-role-complete-profile \ + '($g.profile.content.body.bindings[1]) as $seed | $g.profile.content | .body.bindings += (["ci","execution","forge","identity"] | map(. as $role | $seed | .binding_id=("binding.optional."+$role) | .role=$role | .adapter_instance_id=("instance."+$role) | .principal_id=("principal."+$role) | .execution_boundary_id=("boundary."+$role))) | .body.bindings|=sort_by(.binding_id) | profile_graph::profile_self_ok' +expect_false optional-role-duplicate \ + '($g.profile.content.body.bindings[1]) as $seed | $g.profile.content | .body.bindings += (["ci","execution","forge","identity"] | map(. as $role | $seed | .binding_id=("binding.optional."+$role) | .role=$role | .adapter_instance_id=("instance."+$role) | .principal_id=("principal."+$role) | .execution_boundary_id=("boundary."+$role))) | .body.bindings|=sort_by(.binding_id) | .body.bindings |= map(if .role=="identity" then .role="forge" else . end) | profile_graph::profile_self_ok' + +mapped_false portable-core-profile-graph.test.legacy-121-missing-selection-ref \ + resolved-selection-missing \ + '$g.resolved.content | del(.body.selection_ref) | profile_graph::resolved_profile_shape_ok' +mapped_false portable-core-profile-graph.test.legacy-123-selection-ref-carries-the-wrong-purpose \ + resolved-selection-purpose \ + '$g.resolved.content | .body.selection_ref.purpose="grant" | profile_graph::resolved_profile_shape_ok' +mark_rule portable-core-profile-graph.resolved-profile-body +mapped_false portable-core-profile-graph.test.legacy-125-resolved-bindings-below-the-4-minimum \ + resolved-binding-min \ + '$g.resolved.content | .body.bindings=(.body.bindings[0:3]) | profile_graph::resolved_profile_shape_ok' +mark_rule portable-core-profile-graph.resolved-binding-count +mapped_false portable-core-profile-graph.test.legacy-129-duplicate-tool-id-in-tool-sources \ + resolved-tool-duplicate \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .tool_sources += [.tool_sources[0]] else . end) | profile_graph::resolved_profile_shape_ok' +mark_rule portable-core-profile-graph.resolved-tool-source-set + +mapped_false portable-core-profile-graph.test.legacy-183-resolved-package-source-does-not-match-the-bindings-package-ref \ + resolved-package-projection \ + '$g.resolved.content | .body.bindings[0].package_source.source.object_id=("0"*40) | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.resolved-package-source +expect_false resolved-adapter-id-projection \ + '$g.resolved.content | .body.bindings[0].adapter_implementation.id="manifest.wrong" | profile_graph::resolved_profile_self_ok' +mapped_false portable-core-profile-graph.test.legacy-185-resolved-adapter-implementation-version-does-not-match-the-manifest \ + graph-manifest-version \ + '($g.resolved | .content.body.bindings[0].adapter_implementation.version="v2") as $resolved | profile_graph::profile_set_graph_ok($g.profile;$resolved;$g.manifests)' +mark_rule portable-core-profile-graph.profile-set-manifest-version +mapped_false portable-core-profile-graph.test.legacy-187-resolved-bindings-do-not-cover-the-same-binding-id-set-as-the-profile \ + graph-binding-identities \ + '($g.resolved | .content.body.bindings[-1].binding.binding_id="binding.zzz") as $resolved | profile_graph::profile_set_graph_ok($g.profile;$resolved;$g.manifests)' +mark_rule portable-core-profile-graph.profile-set-binding-identity +mapped_false portable-core-profile-graph.test.legacy-189-mutated-profiles-own-digest-no-longer-matches-the-resolved-profiles-profile \ + refs-profile-bytes-moved \ + '($g.profile | .sha256=("0"*64)) as $profile | profile_graph::profile_set_refs_ok($profile;$g.resolved;$g.manifests)' +mark_rule portable-core-profile-graph.profile-set-profile-ref +mapped_false portable-core-profile-graph.test.legacy-191-a-referenced-manifest-is-simply-not-supplied-profile-resolved-digests-untouc \ + graph-manifest-missing \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests[1:])' +mapped_false portable-core-profile-graph.test.legacy-193-resolved-profile-ref-digest-does-not-match-the-supplied-profile \ + refs-resolved-profile-digest \ + '($g.resolved | .content.body.profile_ref.sha256=("0"*64)) as $resolved | profile_graph::profile_set_refs_ok($g.profile;$resolved;$g.manifests)' +mapped_false portable-core-profile-graph.test.legacy-195-two-supplied-manifests-share-one-document-id \ + refs-manifest-id-duplicate \ + '($g.manifests | .[1].content.id=.[0].content.id) as $manifests | profile_graph::profile_set_refs_ok($g.profile;$g.resolved;$manifests)' +mark_rule portable-core-profile-graph.profile-set-manifest-id-unique +mapped_false portable-core-profile-graph.test.legacy-197-resolved-config-source-present-without-a-config-ref-on-the-binding \ + resolved-config-projection \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="publisher" then .config_source={state:"present",value:.package_source} else . end) | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.profile-set-config-source +expect_false resolved-requested-tool-missing-source \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .tool_sources=[] else . end) | profile_graph::resolved_profile_self_ok' +expect_false resolved-config-source-missing \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .config_source={state:"absent"} else . end) | profile_graph::resolved_profile_self_ok' +expect_false resolved-prompt-source-missing \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .prompt_source={state:"absent"} else . end) | profile_graph::resolved_profile_self_ok' +expect_false resolved-tool-config-source-missing \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .tool_sources[0].config_source={state:"absent"} else . end) | profile_graph::resolved_profile_self_ok' +expect_false resolved-skill-source-missing \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .skill_sources=[] else . end) | profile_graph::resolved_profile_self_ok' +mapped_false portable-core-profile-graph.test.legacy-199-resolved-profile-source-digest-does-not-match-the-supplied-profiles-real-byt \ + resolved-profile-source-digest \ + '$g.resolved.content | .body.profile_source.value_sha256=("0"*64) | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.profile-set-source-digests +mapped_false portable-core-profile-graph.test.legacy-201-resolved-manifest-source-digest-does-not-match-the-supplied-manifests-real-b \ + resolved-manifest-source-digest \ + '$g.resolved.content | .body.bindings[0].manifest_source.value_sha256=("0"*64) | profile_graph::resolved_profile_self_ok' +mapped_false portable-core-profile-graph.test.legacy-203-manifest-does-not-offer-the-bindings-role \ + graph-role-offer \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_role_offer_graph" +mark_rule portable-core-profile-graph.profile-set-role-offer +mapped_false portable-core-profile-graph.test.legacy-205-manifest-does-not-offer-the-bindings-requested-capability \ + graph-capability-offer \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_capability_offer_graph" +mark_rule portable-core-profile-graph.profile-set-capability-offer +mapped_false portable-core-profile-graph.test.legacy-207-two-bindings-claim-different-digests-for-the-same-source-git-object \ + source-claim-conflict \ + '$g.resolved.content.body | .bindings[1].package_source.source=.bindings[0].package_source.source | profile_graph::source_claims_agree(.)' +mark_rule portable-core-profile-graph.source-claim-unique +expect_false source-claim-same-digest-different-format \ + '$g.resolved.content.body | .bindings[1].package_source.source=.bindings[0].package_source.source | .bindings[1].package_source.value_sha256=.bindings[0].package_source.value_sha256 | .bindings[1].package_source.value_format="canonical-json" | profile_graph::source_claims_agree(.)' +expect_false source-claim-profile-manifest-collision \ + '$g.resolved.content.body | .bindings[0].manifest_source.source=.profile_source.source | profile_graph::source_claims_agree(.)' +expect_false source-claim-skill-tool-collision \ + '$g.resolved.content.body | .bindings[0].tool_sources[0].package_source.source=.bindings[0].skill_sources[0].source | profile_graph::source_claims_agree(.)' +expect_true source-claim-consistent-cross-category-reuse \ + '$g.resolved.content.body | .bindings[0].tool_sources[0].package_source=.bindings[0].skill_sources[0] | profile_graph::source_claims_agree(.)' + +mapped_false portable-core-profile-graph.test.legacy-257-embedded-producer-binding-with-emptied-capability-closure \ + resolved-embedded-capability \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .binding.requested_capabilities=[] | .binding.requested_permissions=[] else . end) | profile_graph::resolved_profile_self_ok' +mapped_false portable-core-profile-graph.test.legacy-259-two-embedded-protected-role-bindings-share-one-principal-id \ + resolved-embedded-principal \ + '$g.resolved.content | (.body.bindings[]|select(.binding.role=="producer")|.binding.principal_id) as $shared | .body.bindings |= map(if .binding.role=="reviewer" then .binding.principal_id=$shared else . end) | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.resolved-binding-invariants +mapped_false portable-core-profile-graph.test.legacy-263-stage-run-approves-an-operation-its-embedded-binding-never-requested \ + stage-resolved-capability-route \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .binding.requested_capabilities=[] | .binding.requested_permissions=[] else . end) | profile_graph::resolved_profile_self_ok' + +mapped_false portable-core-profile-graph.test.binding-outside-manifest-offer-rejected \ + review-binding-manifest-offer \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_permission_offer_graph" +mark_rule portable-core-profile-graph.binding-manifest-offer-closure +mapped_false portable-core-profile-graph.test.resolved-source-digest-mismatch-rejected \ + review-source-document-digest \ + '$g.resolved.content | .body.bindings[0].manifest_source.value_sha256=("0"*64) | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.resolved-source-document-digests +mapped_false portable-core-profile-graph.test.unreferenced-manifest-rejected \ + review-unreferenced-manifest \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_extra_graph" +mapped_false portable-core-profile-graph.test.conflicting-source-claim-rejected \ + review-source-single-claim \ + '$g.resolved.content.body | .bindings[1].package_source.source=.bindings[0].package_source.source | profile_graph::source_claims_agree(.)' +mark_rule portable-core-profile-graph.resolved-source-single-claim +mapped_false portable-core-profile-graph.test.dormant-model-binding-rejected \ + review-dormant-deterministic \ + '$g.profile.content | .body.bindings |= map(if .role=="publisher" then .execution_kind="model" | .model_request={provider_id:"p",model_id:"m",effort_id:"e"} | .prompt_ref=.package_ref else . end) | profile_graph::profile_shape_ok' +mark_rule portable-core-profile-graph.dormant-binding-deterministic +mapped_false portable-core-profile-graph.test.resolved-binding-invariant-bypass-rejected \ + review-resolved-binding-invariant \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .binding.requested_capabilities=[] | .binding.requested_permissions=[] else . end) | profile_graph::resolved_profile_self_ok' +mapped_false portable-core-profile-graph.test.resolved-source-projection-standalone-route \ + review-resolved-source-projection \ + '$g.resolved.content | .body.bindings[0].prompt_source.value.source.object_id=("0"*40) | profile_graph::resolved_profile_self_ok' +mark_rule portable-core-profile-graph.resolved-source-self-projections + +expect_true resolved-embedded-field-self-valid \ + '$g.resolved.content | .body.bindings[0].binding.adapter_instance_id="instance.changed" | profile_graph::resolved_profile_self_ok' +expect_false graph-full-embedded-binding-equality \ + '($g.resolved | .content.body.bindings[0].binding.adapter_instance_id="instance.changed") as $resolved | profile_graph::profile_set_graph_ok($g.profile;$resolved;$g.manifests)' + +rebuilt_graphs=( + "$profile_role_offer_graph" + "$profile_capability_offer_graph" + "$profile_execution_offer_graph" + "$profile_permission_offer_graph" + "$profile_tool_version_graph" + "$profile_tool_package_graph" + "$profile_tool_config_presence_graph" + "$profile_tool_config_object_graph" + "$profile_package_graph" + "$profile_config_contract_graph" + "$profile_extra_graph" + "$profile_superset_offer_graph" +) +for rebuilt_index in "${!rebuilt_graphs[@]}"; do + expect_true "rebuilt-$rebuilt_index-refs" \ + 'profile_graph::profile_set_refs_ok($g.profile;$g.resolved;$g.manifests)' \ + "${rebuilt_graphs[$rebuilt_index]}" + expect_true "rebuilt-$rebuilt_index-self-checks" \ + '($g.profile.content|profile_graph::profile_self_ok) and ($g.resolved.content|profile_graph::resolved_profile_self_ok) and all($g.manifests[].content;profile_graph::adapter_manifest_self_ok)' \ + "${rebuilt_graphs[$rebuilt_index]}" +done + +expect_false graph-execution-offer \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_execution_offer_graph" +expect_false graph-full-tool-offer \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_tool_version_graph" +expect_false graph-full-tool-package-offer \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_tool_package_graph" +expect_false graph-full-tool-config-presence \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_tool_config_presence_graph" +expect_false graph-full-tool-config-object \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_tool_config_object_graph" +expect_false graph-package-equality \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_package_graph" +expect_false graph-config-contract \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_config_contract_graph" +expect_true graph-offer-superset \ + 'profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests)' \ + "$profile_superset_offer_graph" +expect_false resolved-prompt-projection \ + '$g.resolved.content | .body.bindings[0].prompt_source.value.source.object_id=("0"*40) | profile_graph::resolved_profile_self_ok' +expect_false resolved-skill-projection \ + '$g.resolved.content | .body.bindings[0].skill_sources[0].source.object_id=("0"*40) | profile_graph::resolved_profile_self_ok' +expect_false resolved-tool-package-projection \ + '$g.resolved.content | .body.bindings[0].tool_sources[0].package_source.source.object_id=("0"*40) | profile_graph::resolved_profile_self_ok' +expect_false resolved-tool-config-projection \ + '$g.resolved.content | .body.bindings[0].tool_sources[0].config_source.value.source.object_id=("0"*40) | profile_graph::resolved_profile_self_ok' +expect_false protected-instance-distinct \ + '$g.profile.content | (.body.bindings[0].adapter_instance_id) as $shared | .body.bindings[1].adapter_instance_id=$shared | profile_graph::profile_self_ok' +expect_false protected-boundary-distinct \ + '$g.profile.content | (.body.bindings[0].execution_boundary_id) as $shared | .body.bindings[1].execution_boundary_id=$shared | profile_graph::profile_self_ok' +expect_false protected-role-complete \ + '$g.profile.content | .body.bindings[1].role="forge" | profile_graph::profile_self_ok' +mark_rule portable-core-profile-graph.protected-role-separation + +expect_true manifest-role-max \ + '$g.manifests[0].content | .body.offered_roles=["ci","execution","forge","identity","producer","publisher","reviewer","verifier"] | profile_graph::adapter_manifest_shape_ok' +expect_false manifest-role-one-over \ + '$g.manifests[0].content | .body.offered_roles=["ci","execution","forge","identity","producer","producer","publisher","reviewer","verifier"] | profile_graph::adapter_manifest_shape_ok' +expect_true manifest-execution-max \ + '$g.manifests[0].content | .body.offered_execution_kinds=["deterministic","model"] | profile_graph::adapter_manifest_shape_ok' +expect_false manifest-execution-one-over \ + '$g.manifests[0].content | .body.offered_execution_kinds=["deterministic","model","model"] | profile_graph::adapter_manifest_shape_ok' +expect_true manifest-capability-max \ + '$g.manifests[0].content | .body.offered_capabilities=["core.harness.produce.v1","core.review.change.v1","core.verify.run.v1"] | profile_graph::adapter_manifest_shape_ok' +expect_false manifest-capability-one-over \ + '$g.manifests[0].content | .body.offered_capabilities=["core.harness.produce.v1","core.review.change.v1","core.verify.run.v1","core.verify.run.v1"] | profile_graph::adapter_manifest_shape_ok' +expect_true manifest-permission-max \ + '$g.manifests[0].content | .body.offered_permissions=["core.perm.candidate.execute.v1","core.perm.evidence.write.v1","core.perm.model.invoke.v1","core.perm.scratch.write.v1","core.perm.target.read.v1"] | profile_graph::adapter_manifest_shape_ok' +expect_false manifest-permission-one-over \ + '$g.manifests[0].content | .body.offered_permissions=["core.perm.candidate.execute.v1","core.perm.evidence.write.v1","core.perm.model.invoke.v1","core.perm.scratch.write.v1","core.perm.target.read.v1","core.perm.target.read.v1"] | profile_graph::adapter_manifest_shape_ok' +expect_true manifest-tool-max \ + '$g.manifests[0].content | .body.offered_tools=([range(1;33) as $i | (.body.offered_tools[0] | .tool_id=("tool."+("x"*$i)))] | sort_by(.tool_id)) | profile_graph::adapter_manifest_shape_ok' +expect_false manifest-tool-one-over \ + '$g.manifests[0].content | .body.offered_tools=([range(1;34) as $i | (.body.offered_tools[0] | .tool_id=("tool."+("x"*$i)))] | sort_by(.tool_id)) | profile_graph::adapter_manifest_shape_ok' +expect_true profile-binding-max \ + '$g.profile.content | .body.bindings += [range(1;5) as $i | (.body.bindings[1] | .binding_id=("binding.x"+("x"*$i)))] | .body.bindings|=sort_by(.binding_id) | profile_graph::profile_shape_ok' +expect_false profile-binding-one-over \ + '$g.profile.content | .body.bindings += [range(1;6) as $i | (.body.bindings[1] | .binding_id=("binding.x"+("x"*$i)))] | .body.bindings|=sort_by(.binding_id) | profile_graph::profile_shape_ok' +expect_true profile-skill-max \ + '$g.profile.content | .body.bindings[0].skill_refs=([range(1;33) as $i | (.body.bindings[0].skill_refs[0] | .location.value=("skills/"+("x"*$i)))] | sort_by(.location.value)) | profile_graph::profile_shape_ok' +expect_false profile-skill-one-over \ + '$g.profile.content | .body.bindings[0].skill_refs=([range(1;34) as $i | (.body.bindings[0].skill_refs[0] | .location.value=("skills/"+("x"*$i)))] | sort_by(.location.value)) | profile_graph::profile_shape_ok' +expect_true profile-tool-max \ + '$g.profile.content | .body.bindings[0].requested_tools=([range(1;33) as $i | (.body.bindings[0].requested_tools[0] | .tool_id=("tool."+("x"*$i)))] | sort_by(.tool_id)) | profile_graph::profile_shape_ok' +expect_false profile-tool-one-over \ + '$g.profile.content | .body.bindings[0].requested_tools=([range(1;34) as $i | (.body.bindings[0].requested_tools[0] | .tool_id=("tool."+("x"*$i)))] | sort_by(.tool_id)) | profile_graph::profile_shape_ok' +expect_true resolved-binding-max \ + '$g.resolved.content | .body.bindings += [range(1;5) as $i | (.body.bindings[1] | .binding.binding_id=("binding.x"+("x"*$i)))] | .body.bindings|=sort_by(.binding.binding_id) | profile_graph::resolved_profile_shape_ok' +expect_false resolved-binding-one-over \ + '$g.resolved.content | .body.bindings += [range(1;6) as $i | (.body.bindings[1] | .binding.binding_id=("binding.x"+("x"*$i)))] | .body.bindings|=sort_by(.binding.binding_id) | profile_graph::resolved_profile_shape_ok' +expect_true resolved-skill-max \ + '$g.resolved.content | .body.bindings[0].skill_sources=([range(1;33) as $i | (.body.bindings[0].skill_sources[0] | .source.location.value=("skills/"+("x"*$i)))] | sort_by(.source.location.value)) | profile_graph::resolved_profile_shape_ok' +expect_false resolved-skill-one-over \ + '$g.resolved.content | .body.bindings[0].skill_sources=([range(1;34) as $i | (.body.bindings[0].skill_sources[0] | .source.location.value=("skills/"+("x"*$i)))] | sort_by(.source.location.value)) | profile_graph::resolved_profile_shape_ok' +expect_true resolved-tool-max \ + '$g.resolved.content | .body.bindings[0].tool_sources=([range(1;33) as $i | (.body.bindings[0].tool_sources[0] | .tool_id=("tool."+("x"*$i)))] | sort_by(.tool_id)) | profile_graph::resolved_profile_shape_ok' +expect_false resolved-tool-one-over \ + '$g.resolved.content | .body.bindings[0].tool_sources=([range(1;34) as $i | (.body.bindings[0].tool_sources[0] | .tool_id=("tool."+("x"*$i)))] | sort_by(.tool_id)) | profile_graph::resolved_profile_shape_ok' + +expect_layer layer-shape E_SHAPE \ + '$g.profile.content | .body.bindings[0].requested_capabilities=["core.verify.run.v1"] | if profile_graph::document_shape_ok then "OK" else "E_SHAPE" end' +expect_layer layer-self-relation E_RELATION \ + '$g.profile.content | del(.body.bindings[1].authority_ref) | if (profile_graph::document_shape_ok|not) then "E_SHAPE" elif profile_graph::document_self_ok then "OK" else "E_RELATION" end' +expect_layer layer-cross-ref E_REF \ + '($g.profile | .sha256=("0"*64)) as $profile | if profile_graph::profile_set_refs_ok($profile;$g.resolved;$g.manifests) then "OK" else "E_REF" end' +expect_layer layer-cross-relation E_RELATION \ + 'if (profile_graph::profile_set_refs_ok($g.profile;$g.resolved;$g.manifests[1:])|not) then "E_REF" elif profile_graph::profile_set_graph_ok($g.profile;$g.resolved;$g.manifests[1:]) then "OK" else "E_RELATION" end' +expect_layer layer-resolved-embedded-capability E_RELATION \ + '$g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .binding.requested_capabilities=[] | .binding.requested_permissions=[] else . end) | if (profile_graph::document_shape_ok|not) then "E_SHAPE" elif profile_graph::document_self_ok then "OK" else "E_RELATION" end' + +route_case() { + local case_id="$1" + local expected="$2" + local expression="$3" + local class="$4" + local actual + if [ "$class" = cell ]; then + profile_cell_total=$((profile_cell_total + 1)) + else + profile_forced_total=$((profile_forced_total + 1)) + fi + if ! actual="$("${profile_jq_command[@]}" -L "$profile_module_dir" \ + --slurpfile graph "$profile_graph" -n \ + 'import "profile_graph" as profile_graph; + def route_document($document): + $document | profile_graph::document_self_ok; + def route_profile_set($profile;$resolved;$manifests): + all($manifests[].content;profile_graph::adapter_manifest_self_ok) and + ($profile.content|profile_graph::profile_self_ok) and + ($resolved.content|profile_graph::resolved_profile_self_ok) and + profile_graph::profile_set_refs_ok($profile;$resolved;$manifests) and + profile_graph::profile_set_graph_ok($profile;$resolved;$manifests); + def route_stage_run($resolved): + $resolved.content | profile_graph::resolved_profile_self_ok; + $graph[0] as $g | ('"$expression"')')"; then + fail_case "$case_id raised a jq route error" + return + fi + if [ "$actual" = "$expected" ]; then + if [ "$class" = cell ]; then + profile_cell_passed=$((profile_cell_passed + 1)) + else + profile_forced_passed=$((profile_forced_passed + 1)) + fi + else + fail_case "$case_id expected $expected, got $actual" + fi +} + +route_case cell-document-manifest true \ + 'route_document($g.manifests[0].content)' cell +route_case cell-profile-set-manifest true \ + 'route_profile_set($g.profile;$g.resolved;$g.manifests)' cell +route_case cell-document-profile true \ + 'route_document($g.profile.content)' cell +route_case cell-profile-set-profile true \ + 'route_profile_set($g.profile;$g.resolved;$g.manifests)' cell +route_case cell-document-resolved true \ + 'route_document($g.resolved.content)' cell +route_case cell-profile-set-resolved true \ + 'route_profile_set($g.profile;$g.resolved;$g.manifests)' cell +route_case cell-stage-run-resolved true \ + 'route_stage_run($g.resolved)' cell +route_case cell-profile-set-graph true \ + 'route_profile_set($g.profile;$g.resolved;$g.manifests)' cell + +route_case forced-document-manifest false \ + 'route_document($g.manifests[0].content | .body.extra=true)' forced +route_case forced-profile-set-manifest false \ + 'route_profile_set($g.profile;$g.resolved;($g.manifests | .[0].content.body.extra=true))' forced +route_case forced-document-profile false \ + 'route_document($g.profile.content | .body.extra=true)' forced +route_case forced-profile-set-profile false \ + 'route_profile_set(($g.profile | .content.body.extra=true);$g.resolved;$g.manifests)' forced +route_case forced-document-resolved false \ + 'route_document($g.resolved.content | .body.bindings |= map(if .binding.role=="producer" then .binding.requested_capabilities=[] | .binding.requested_permissions=[] else . end))' forced +route_case forced-profile-set-resolved false \ + 'route_profile_set($g.profile;($g.resolved | .content.body.bindings[0].package_source.source.object_id=("0"*40));$g.manifests)' forced +route_case forced-stage-run-resolved false \ + 'route_stage_run($g.resolved | .content.body.bindings |= map(if .binding.role=="producer" then .binding.requested_capabilities=[] | .binding.requested_permissions=[] else . end))' forced +route_case forced-profile-set-graph false \ + 'route_profile_set($g.profile;$g.resolved;$g.manifests[1:])' forced +mark_rule portable-core-profile-graph.document-routes +mark_rule portable-core-profile-graph.profile-set-route +mark_rule portable-core-profile-graph.stage-run-resolved-route +mark_test portable-core-profile-graph.test.legacy-261-resolved-profile-self-forced-route-document +mark_test portable-core-profile-graph.test.legacy-265-resolved-profile-self-forced-route-stage-run + +profile_import_guard_ok() { + local candidate="$1" + [ "$(head -n 1 "$candidate")" = 'import "schema" as schema;' ] && + [ "$(grep -Fxc 'import "schema" as schema;' "$candidate")" -eq 1 ] && + ! tail -n +2 "$candidate" | + grep -Eq '(^|[^a-zA-Z0-9_])(import|include|module)([^a-zA-Z0-9_]|$)' && + ! grep -Eq 'search[[:space:]]*:|import[[:space:]]*\"[^\"]+\"[[:space:]]+as[[:space:]]+[^;]+[[:space:]]+\{' "$candidate" +} + +profile_guard_total=$((profile_guard_total + 1)) +if profile_import_guard_ok "$profile_module"; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "fixed schema-only import" +fi + +for directive_case in compact-import multiline-import compact-include metadata-import escaped-import module-directive; do + directive_file="$profile_tmp/$directive_case.jq" + cp "$profile_module" "$directive_file" + case "$directive_case" in + compact-import) printf '%s\n' 'import"evil"as evil;' >> "$directive_file" ;; + multiline-import) printf '%s\n' 'import' ' "evil"' ' as evil;' >> "$directive_file" ;; + compact-include) printf '%s\n' 'include"evil";' >> "$directive_file" ;; + metadata-import) printf '%s\n' 'import "evil" as evil {search:"."};' >> "$directive_file" ;; + escaped-import) printf '%s\n' 'import"ev\u0069l"as evil;' >> "$directive_file" ;; + module-directive) printf '%s\n' 'module {name:"evil"};' >> "$directive_file" ;; + esac + profile_guard_total=$((profile_guard_total + 1)) + if ! profile_import_guard_ok "$directive_file"; then + profile_guard_passed=$((profile_guard_passed + 1)) + else + fail_case "fixed import guard accepted $directive_case" + fi +done +mark_rule portable-core-profile-graph.fixed-schema-import + +current_blob_ok() { + local repo="$1" path="$2" expected_oid="$3" mode type oid actual_path + IFS=$' \t' read -r mode type oid actual_path < <(git -C "$repo" ls-tree HEAD -- "$path") || return 1 + [ "$mode" = 100644 ] && [ "$type" = blob ] && + [ "$oid" = "$expected_oid" ] && [ "$actual_path" = "$path" ] +} + +profile_guard_total=$((profile_guard_total + 1)) +if current_blob_ok "$profile_root" "$profile_schema_path" "$profile_schema_oid" && + current_blob_ok "$profile_root" "$profile_registry_path" "$profile_registry_oid"; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "schema G3 dependency pin" +fi +mark_rule portable-core-profile-graph.schema-dependency-pin + +profile_guard_total=$((profile_guard_total + 1)) +if current_blob_ok "$profile_root" "$profile_ingress_path" "$profile_ingress_oid"; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "ingress serial predecessor receipt" +fi +mark_rule portable-core-profile-graph.serial-predecessor-receipt + +profile_guard_total=$((profile_guard_total + 1)) +if [ "$("${profile_jq_command[@]}" -r '.construction_base' <<< "$fixture_metadata")" = "$profile_base" ] && + [ "$("${profile_jq_command[@]}" -r '.generation_id' <<< "$fixture_metadata")" = "$profile_generation" ] && + [ "$("${profile_jq_command[@]}" -r '.parent_spec_blob' <<< "$fixture_metadata")" = "$profile_parent_spec" ] && + [ "$("${profile_jq_command[@]}" -r '.schema_export_oid' <<< "$fixture_metadata")" = "$profile_schema_oid" ] && + [ "$("${profile_jq_command[@]}" -r '.schema_g3_comment' <<< "$fixture_metadata")" -eq 5466181650 ] && + [ "$("${profile_jq_command[@]}" -r '.schema_merge_commit' <<< "$fixture_metadata")" = "$profile_schema_merge" ] && + [ "$("${profile_jq_command[@]}" -r '.ingress_export_oid' <<< "$fixture_metadata")" = "$profile_ingress_oid" ] && + [ "$("${profile_jq_command[@]}" -r '.ingress_g3_comment' <<< "$fixture_metadata")" -eq 5468279667 ] && + [ "$("${profile_jq_command[@]}" -r '.registry_oid' <<< "$fixture_metadata")" = "$profile_registry_oid" ]; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "fixture identity and predecessor record" +fi + +shallow_repo="$profile_tmp/shallow-checkout" +git -c init.defaultObjectFormat=sha1 init -q --object-format=sha1 --template= -b main "$shallow_repo" +mkdir -p "$shallow_repo/$(dirname "$profile_schema_path")" +cp "$profile_root/$profile_schema_path" "$shallow_repo/$profile_schema_path" +cp "$profile_root/$profile_ingress_path" "$shallow_repo/$profile_ingress_path" +cp "$profile_root/$profile_registry_path" "$shallow_repo/$profile_registry_path" +GIT_ATTR_NOSYSTEM=1 git -C "$shallow_repo" -c core.attributesFile=/dev/null -c core.autocrlf=false add . +git -C "$shallow_repo" -c core.hooksPath=/dev/null -c user.name=proof -c user.email=proof@example.invalid -c commit.gpgSign=false commit -qm tip +printf '%s\n' "$(git -C "$shallow_repo" rev-parse HEAD)" > "$shallow_repo/.git/shallow" +profile_guard_total=$((profile_guard_total + 1)) +if [ "$(git -C "$shallow_repo" rev-parse --is-shallow-repository)" = true ] && + ! git -C "$shallow_repo" cat-file -e "$profile_schema_merge^{commit}" 2>/dev/null && + current_blob_ok "$shallow_repo" "$profile_schema_path" "$profile_schema_oid" && + current_blob_ok "$shallow_repo" "$profile_ingress_path" "$profile_ingress_oid" && + current_blob_ok "$shallow_repo" "$profile_registry_path" "$profile_registry_oid"; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "history-absent dependency proof" +fi + +generation_files="$profile_tmp/generation-files" +find "$profile_root/core/v1/generations/$profile_generation" -type f -print | + sed "s#^$profile_root/##" | LC_ALL=C sort > "$generation_files" + +private_generation_path_ok() { + case "$1" in + "core/v1/generations/$profile_generation/core-ingress.sh"|\ + "core/v1/generations/$profile_generation/modules/schema.jq"|\ + "core/v1/generations/$profile_generation/modules/profile_graph.jq"|\ + "core/v1/generations/$profile_generation/modules/stage_request.jq"|\ + "core/v1/generations/$profile_generation/modules/result_facts.jq"|\ + "core/v1/generations/$profile_generation/modules/result_truth.jq") return 0 ;; + *) return 1 ;; + esac +} + +private_generation_paths_ok() { + local candidate_file="$1" + local candidate_path + while IFS= read -r candidate_path; do + [ -n "$candidate_path" ] || continue + private_generation_path_ok "$candidate_path" || return 1 + done < "$candidate_file" +} + +profile_guard_total=$((profile_guard_total + 1)) +if [ "$(grep -Fxc "core/v1/generations/$profile_generation/modules/profile_graph.jq" "$generation_files")" -eq 1 ] && + private_generation_paths_ok "$generation_files" && + [ ! -e "$profile_root/core/v1/generations/$profile_generation/contracts.jq" ] && + [ ! -e "$profile_root/scripts/core-contract.sh" ] && + [ -z "$(find "$profile_root/core/v1/generations/$profile_generation" -type l -print -quit)" ]; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "private activation guard" +fi + +planned_generation_files="$profile_tmp/planned-generation-files" +cp "$generation_files" "$planned_generation_files" +printf '%s\n' \ + "core/v1/generations/$profile_generation/modules/stage_request.jq" \ + "core/v1/generations/$profile_generation/modules/result_facts.jq" \ + "core/v1/generations/$profile_generation/modules/result_truth.jq" >> \ + "$planned_generation_files" +profile_guard_total=$((profile_guard_total + 1)) +if private_generation_paths_ok "$planned_generation_files"; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "private guard rejected planned downstream modules" +fi + +unknown_generation_files="$profile_tmp/unknown-generation-files" +cp "$generation_files" "$unknown_generation_files" +printf '%s\n' "core/v1/generations/$profile_generation/modules/unknown.jq" >> \ + "$unknown_generation_files" +profile_guard_total=$((profile_guard_total + 1)) +if ! private_generation_paths_ok "$unknown_generation_files"; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "private guard accepted an unknown generation member" +fi +mark_rule portable-core-profile-graph.private-activation-guard + +required_paths="core/v1/generations/$profile_generation/modules/profile_graph.jq +scripts/test/portable-core-profile-graph-fixtures.jq +scripts/test/portable-core-profile-graph-ledger.tsv +scripts/test/portable-core-profile-graph.test.sh" +profile_guard_total=$((profile_guard_total + 1)) +manifest_ok=true +while IFS= read -r required_path; do + [ "$(grep -Fxc "$required_path" "$profile_manifest" || true)" -eq 1 ] && + [ -f "$profile_root/$required_path" ] || manifest_ok=false +done <<< "$required_paths" +if [ "$manifest_ok" = true ]; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "restore manifest coverage" +fi + +manifest_prefix="$profile_tmp/manifest-prefix" +head -n 100 "$profile_manifest" > "$manifest_prefix" +profile_guard_total=$((profile_guard_total + 1)) +if [ "$(sha256_path "$manifest_prefix")" = \ + "05df3e94698afb36978d4a48e0993db8c4a02bb793b89a095372626cd5a7bfbd" ] && + [ "$(sed -n '101,104p' "$profile_manifest")" = "$required_paths" ]; then + profile_guard_passed=$((profile_guard_passed + 1)) +else + fail_case "restore manifest exact append" +fi +mark_rule portable-core-profile-graph.restore-manifest + +profile_review_total="$(awk -F '\t' 'NR>1 && $1=="review" {n++} END {print n+0}' "$profile_ledger")" +profile_legacy_total="$(awk -F '\t' 'NR>1 && $1=="legacy" {n++} END {print n+0}' "$profile_ledger")" +if [ "$profile_review_total" -ne 7 ] || [ "$profile_legacy_total" -ne 92 ] || + [ "$(tail -n +2 "$profile_ledger" | cut -f2 | sort -u | wc -l | tr -d ' ')" -ne 99 ] || + [ "$(awk -F '\t' 'NR>1 && $3=="ported" {n++} END {print n+0}' "$profile_ledger")" -ne 95 ] || + [ "$(awk -F '\t' 'NR>1 && $3=="replaced-by" {n++} END {print n+0}' "$profile_ledger")" -ne 4 ] || + [ "$(sha256_path "$profile_ledger")" != \ + "$("${profile_jq_command[@]}" -r '.profile_mapping_sha256' <<< "$fixture_metadata")" ] || + [ "$("${profile_jq_command[@]}" -r '.review_rows' <<< "$fixture_metadata")" -ne 7 ] || + [ "$("${profile_jq_command[@]}" -r '.legacy_rows' <<< "$fixture_metadata")" -ne 92 ] || + [ "$("${profile_jq_command[@]}" -r '.review_source_sha256' <<< "$fixture_metadata")" != \ + "31793a3ad42acf4df117ea158a78738e056bae550269483870487c3e146b27f9" ] || + [ "$("${profile_jq_command[@]}" -r '.legacy_source_sha256' <<< "$fixture_metadata")" != \ + "3d5a6fb192f9bcaba5c4b89314d30f88a03b9d8a1e1e634297c267b14f096092" ]; then + fail_case "frozen ledger row inventory" +fi + +profile_expected_rules="$profile_tmp/expected-rules" +cut -f4 "$profile_ledger" | tail -n +2 > "$profile_expected_rules" +printf '%s\n' \ + portable-core-profile-graph.document-routes \ + portable-core-profile-graph.fixed-schema-import \ + portable-core-profile-graph.private-activation-guard \ + portable-core-profile-graph.profile-set-route \ + portable-core-profile-graph.protected-role-separation \ + portable-core-profile-graph.restore-manifest \ + portable-core-profile-graph.schema-dependency-pin \ + portable-core-profile-graph.serial-predecessor-receipt \ + portable-core-profile-graph.stage-run-resolved-route >> "$profile_expected_rules" +LC_ALL=C sort -u "$profile_expected_rules" > "$profile_tmp/expected-rules.sorted" +LC_ALL=C sort -u "$profile_seen_rules" > "$profile_tmp/seen-rules.sorted" +if ! cmp -s "$profile_tmp/expected-rules.sorted" "$profile_tmp/seen-rules.sorted"; then + fail_case "owned rule inventory" +fi + +tail -n +2 "$profile_ledger" | cut -f5 | LC_ALL=C sort -u > "$profile_tmp/expected-tests" +LC_ALL=C sort -u "$profile_seen_tests" > "$profile_tmp/seen-tests.sorted" +if ! cmp -s "$profile_tmp/expected-tests" "$profile_tmp/seen-tests.sorted"; then + fail_case "ledger stable test inventory" +fi + +profile_review_accounted="$(awk -F '\t' ' + NR==FNR {seen[$1]=1; next} + FNR>1 && $1=="review" && ($5 in seen) {n++} + END {print n+0} +' "$profile_tmp/seen-tests.sorted" "$profile_ledger")" +profile_legacy_accounted="$(awk -F '\t' ' + NR==FNR {seen[$1]=1; next} + FNR>1 && $1=="legacy" && ($5 in seen) {n++} + END {print n+0} +' "$profile_tmp/seen-tests.sorted" "$profile_ledger")" +if [ "$profile_review_accounted" -ne 7 ] || [ "$profile_legacy_accounted" -ne 92 ]; then + fail_case "ledger executed mapping" +fi + +profile_owned_total="$(wc -l < "$profile_tmp/expected-rules.sorted" | tr -d ' ')" +if [ "$profile_owned_total" -ne \ + "$("${profile_jq_command[@]}" -r '.owned_rules' <<< "$fixture_metadata")" ] || + [ "$profile_direct_total" -ne \ + "$("${profile_jq_command[@]}" -r '.direct_cases' <<< "$fixture_metadata")" ] || + [ "$profile_cell_total" -ne \ + "$("${profile_jq_command[@]}" -r '.command_to_rule_cells' <<< "$fixture_metadata")" ] || + [ "$profile_forced_total" -ne \ + "$("${profile_jq_command[@]}" -r '.forced_routes' <<< "$fixture_metadata")" ] || + [ "$profile_layer_total" -ne \ + "$("${profile_jq_command[@]}" -r '.error_layer_cases' <<< "$fixture_metadata")" ] || + [ "$profile_guard_total" -ne \ + "$("${profile_jq_command[@]}" -r '.guard_cases' <<< "$fixture_metadata")" ]; then + fail_case "fixed proof denominators" +fi +profile_owned_passed="$profile_owned_total" +if [ "$profile_failures" -ne 0 ]; then + profile_owned_passed=0 +fi + +printf 'owned rules: %s/%s\n' "$profile_owned_passed" "$profile_owned_total" +printf 'direct cases: %s/%s\n' "$profile_direct_passed" "$profile_direct_total" +printf 'command-to-rule cells: %s/%s\n' "$profile_cell_passed" "$profile_cell_total" +printf 'forced routes: %s/%s\n' "$profile_forced_passed" "$profile_forced_total" +printf 'error-layer cases: %s/%s\n' "$profile_layer_passed" "$profile_layer_total" +printf 'activation/restore/dependency cases: %s/%s\n' "$profile_guard_passed" "$profile_guard_total" +printf 'review findings accounted for: %s/%s\n' "$profile_review_accounted" "$profile_review_total" +printf 'legacy assertions accounted for: %s/%s\n' "$profile_legacy_accounted" "$profile_legacy_total" +printf 'failures: %s\n' "$profile_failures" + +[ "$profile_failures" -eq 0 ]