diff --git a/.trinity/seals/claw_ClawSocialVisuals.json b/.trinity/seals/claw_ClawSocialVisuals.json new file mode 100644 index 0000000000..3c238836ce --- /dev/null +++ b/.trinity/seals/claw_ClawSocialVisuals.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [ + { + "spec": "specs/t27f/reel_factory.t27", + "spec_hash": "sha256:96099d764ba86fa3ef2c29d81c3838ce255bfd3b3abbd37b27cb86e4bca91012" + }, + { + "spec": "specs/t27f/reel_topics.t27", + "spec_hash": "sha256:7c1762cde96524791711594d23f880fbad1f497a499277710f1fd61d52d50157" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:5b6eef8669d969c87adf78dbeb3714c79aff86a0b632ec4c2ce651242764a956", + "gen_hash_rust": "sha256:f2641a89d573d6e171b2de6ada48c92d3637d31e2faa4f732cbe782a1223d91e", + "gen_hash_verilog": "sha256:1e9383041fd8e9e0de34703745e40b7f8407e7e7de8994a80033f64c2baa9eb8", + "gen_hash_zig": "sha256:77c5dd0532b72be706c5c86049d9d14c4b0264f01fac84f659b54d50ca5b5782", + "module": "ClawSocialVisuals", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T17:41:17Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:0ec92c5f242f768409c76df5cd30143e7e140bf293f87744e98564207734546a", + "spec_path": "specs/claw/social_visuals.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 14, + "total": 14, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/skills_skill_t27_social_visual_consent.json b/.trinity/seals/skills_skill_t27_social_visual_consent.json new file mode 100644 index 0000000000..14a5d415d3 --- /dev/null +++ b/.trinity/seals/skills_skill_t27_social_visual_consent.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:646f8e491c165de179566891f233c5858abc0536f241bc7a542ad489e0d25c0a", + "gen_hash_rust": "sha256:cffd9e21744015fc1999ee38a35797e2b0b7e000b33c6f301787d6670900a638", + "gen_hash_verilog": "sha256:fcdc3897118f0c20a519eea8794208543b2bed2a22526408ebc9be021eec9cdc", + "gen_hash_zig": "sha256:9d0a1681a071ffca5da13fad184113aeee46b3b20a20e807ca6479e9066f2494", + "module": "skill_t27_social_visual_consent", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T17:41:18Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:851f316ed3db9e6a1ed4652ea497be3bfe165630ab7c78701ec4cd3e156ed54e", + "spec_path": "specs/skills/t27-social-visual-consent.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 1, + "total": 1, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/skills_skill_t27_social_visual_format.json b/.trinity/seals/skills_skill_t27_social_visual_format.json new file mode 100644 index 0000000000..0429424231 --- /dev/null +++ b/.trinity/seals/skills_skill_t27_social_visual_format.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:fe8bc021c5bcbbedb829dab4c14a4896a895afd5d6a2a2c94ac281e0a3e5839e", + "gen_hash_rust": "sha256:8eba4403e5fce6c315ddddb0e2f821be761b9f6fb8259edd6f196ae332b210a0", + "gen_hash_verilog": "sha256:fc5f464911ff4a7a6e7db399a5cdd094fbb8a7c072155807f15cd280b94333c1", + "gen_hash_zig": "sha256:8adb6041d6b1e130d4772a75778e73d7766235dc7ff08ba2f2aef19c55588fa6", + "module": "skill_t27_social_visual_format", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T17:41:17Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:3fd4e5ec5e00c8586d5f8946b7e10bab32f6a870c6f4ab7bce1bb489e63bb8df", + "spec_path": "specs/skills/t27-social-visual-format.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 1, + "total": 1, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/skills_skill_t27_social_visual_gate.json b/.trinity/seals/skills_skill_t27_social_visual_gate.json new file mode 100644 index 0000000000..56f393c596 --- /dev/null +++ b/.trinity/seals/skills_skill_t27_social_visual_gate.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:d4b4e308ff32295b5f23c737d171c8dd8879152d370acfa17098ba34372146f5", + "gen_hash_rust": "sha256:53d26cd8ccb06eb7f5a98730463fe4ea86239b563b2fd61feaa9ff607b8d02fc", + "gen_hash_verilog": "sha256:c4d8f840c3ee7fb8c9d4f565279687eaa89b3cc65cc6c0d8313150725705af02", + "gen_hash_zig": "sha256:0f14003642c8416991157a40190b0821a548c1b69882ad0d127dc3aad5dc1a66", + "module": "skill_t27_social_visual_gate", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T17:41:18Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:dd530b5c7acefe1599460a526999334919822a66f71a83d34e5027c612fcc121", + "spec_path": "specs/skills/t27-social-visual-gate.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 1, + "total": 1, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/skills_skill_t27_social_visual_scene.json b/.trinity/seals/skills_skill_t27_social_visual_scene.json new file mode 100644 index 0000000000..9be32d050b --- /dev/null +++ b/.trinity/seals/skills_skill_t27_social_visual_scene.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:c27d3e0e630d4df83e9a9ad530cad48a3ba85fc12dfb57f7460d867b16d59ea4", + "gen_hash_rust": "sha256:06a2736f465e81376f0a1c3f2b4f102fef0c1f3b503d0752b41b666889a6e057", + "gen_hash_verilog": "sha256:022373764efbbf261aca62c818c93631f524b328d99ab2acfddfe96e1fdaccc6", + "gen_hash_zig": "sha256:dc2709bcc539e355e4e3ba2c99d9b604eb3f66ef144c7d4dc7d666e522be8e93", + "module": "skill_t27_social_visual_scene", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T17:41:18Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:87052136deb0aeb55252a1d8567f3ce959912c5828ad8ce6b91bd249dc9cc69d", + "spec_path": "specs/skills/t27-social-visual-scene.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 1, + "total": 1, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 6e4b977d5f..3eb4ec51cd 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -197,6 +197,7 @@ {"path": "specs/claw/sarif_feed.t27", "t27b": "pass"}, {"path": "specs/claw/sarif_pins.t27", "t27b": "pass"}, {"path": "specs/claw/service.t27", "t27b": "pass"}, + {"path": "specs/claw/social_visuals.t27", "t27b": "pass"}, {"path": "specs/claw/suggest.t27", "t27b": "pass"}, {"path": "specs/claw/suggest_feed.t27", "t27b": "pass"}, {"path": "specs/claw/t27f_gate.t27", "t27b": "pass"}, @@ -1407,6 +1408,10 @@ {"path": "specs/skills/t27-phi-loop.t27", "t27b": "pass"}, {"path": "specs/skills/t27-providers-steward.t27", "t27b": "pass"}, {"path": "specs/skills/t27-self-host.t27", "t27b": "pass"}, + {"path": "specs/skills/t27-social-visual-consent.t27", "t27b": "pass"}, + {"path": "specs/skills/t27-social-visual-format.t27", "t27b": "pass"}, + {"path": "specs/skills/t27-social-visual-gate.t27", "t27b": "pass"}, + {"path": "specs/skills/t27-social-visual-scene.t27", "t27b": "pass"}, {"path": "specs/skills/t27-t27f.t27", "t27b": "pass"}, {"path": "specs/skills/t27-tri-pipeline.t27", "t27b": "pass"}, {"path": "specs/skills/t27-tri.t27", "t27b": "pass"}, diff --git a/specs/claw/social_visuals.t27 b/specs/claw/social_visuals.t27 new file mode 100644 index 0000000000..e43c4e293c --- /dev/null +++ b/specs/claw/social_visuals.t27 @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/social_visuals.t27 -- what TRI CLAW does when it is asked for a social-media visual: +; the four decisions behind the skill cards specs/skills/t27-social-visual-*.t27. Issue +; gHashTag/t27#10570 (R5), epic gHashTag/t27#10560. +; -- +; WHY: the owner, 2026-10-19: every reel and social visual must differ and come from its own +; content, and TRI CLAW must be trained in as many social-visual skills as possible. The factory +; (specs/t27f/reel_factory.t27) decides what an item looks like. This law decides what TRI CLAW +; does with that plan, so that it never picks a size by feel, never invents a scene, never +; publishes a near-duplicate and never posts without the owner. +; -- +; The four decisions, each a section below, each with its card: +; format which FMT_* a (channel, kind) pair gets, or "unsupported" card t27-social-visual-format +; scene which archetype an item gets: reel_factory::arche, nothing else card t27-social-visual-scene +; gate may this set of stills go on: count, pairs measured, worst SSIM card t27-social-visual-gate +; consent may this post go out: gate passed AND the owner's yes for it card t27-social-visual-consent +; -- +; One home for each fact. The sizes, the moving flags, the archetypes, the course length and the +; similarity bound are reel_factory's and reel_topics'; they are imported here, never copied. A +; test below that reads a size reads it from the factory. +; -- +; Fail closed. An unsupported pair is FMTS, an item with no plan is ARCHS, an unmeasured set does +; not pass, a missing digest does not match another missing digest. Nothing unread is success. +; -- +; The honest limits: +; - The channel-to-format table (pick_format) is this module's choice among reel_factory's eight +; formats, like TITLE_WEIGHT there: it is chosen, not read from each network's current rules. +; A pair it has no format for is answered "unsupported", which is a gap, not a ruling that the +; network cannot show it. +; - The factory plans the 27 lessons of the course. A blog post or a new course arrives as data in +; a later step of the epic; until then scene_for refuses it. +; - The gate compares the stills of one run with each other. It does not compare them with posts +; published earlier, and the bound it uses is reel_factory's SIMILAR_MAX_PERMILLE, which that +; spec says is a starting value not yet measured on rendered stills (R3). +; - Consent here is a comparison of values the publisher passes in. Computing the digest of the +; exact bytes of a post, and knowing whether a message came from the owner's chat, belong to the +; host that publishes; this law says what to do with the answers and has no way to widen them. +; Publishing itself is out of this module and out of the epic's scope. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawSocialVisuals; + +use t27f::reel_factory::arche; +use t27f::reel_factory::content; +use t27f::reel_factory::too_similar; +use t27f::reel_factory::carousel_slides; +use t27f::reel_factory::ARCHS; +use t27f::reel_factory::FMTS; +use t27f::reel_factory::FMT_REEL_TALL; +use t27f::reel_factory::FMT_REEL_WIDE; +use t27f::reel_factory::FMT_SQUARE; +use t27f::reel_factory::FMT_PORTRAIT; +use t27f::reel_factory::FMT_COVER; +use t27f::reel_factory::FMT_CAROUSEL; +use t27f::reel_factory::FMT_X_CARD; +use t27f::reel_factory::FMT_OG; +use t27f::reel_topics::TOPICS; + +// ---- format: which size a channel gets ---- + +pub const CH_INSTAGRAM : usize = 0; +pub const CH_TIKTOK : usize = 1; +pub const CH_YT_SHORTS : usize = 2; +pub const CH_YOUTUBE : usize = 3; +pub const CH_X : usize = 4; +pub const CH_LINKEDIN : usize = 5; +pub const CH_TELEGRAM : usize = 6; +pub const CH_SITE : usize = 7; +pub const CHANNELS : usize = 8; + +// What is asked for. +pub const KIND_MOVING : usize = 0; // a reel: video with the voice and the music +pub const KIND_STILL : usize = 1; // one picture post +pub const KIND_CAROUSEL : usize = 2; // a set of slides +pub const KIND_COVER : usize = 3; // the picture shown before a reel plays +pub const KIND_LINK : usize = 4; // the card a shared link unfolds into +pub const KINDS : usize = 5; + +// The format for (channel, kind), or FMTS when this law has none for the pair. +pub fn pick_format(ch: usize, kind: usize) -> usize { + if (kind == KIND_MOVING) { + if (ch == CH_INSTAGRAM || ch == CH_TIKTOK || ch == CH_YT_SHORTS || ch == CH_TELEGRAM) { + return FMT_REEL_TALL; + } + if (ch == CH_YOUTUBE || ch == CH_X || ch == CH_LINKEDIN) { + return FMT_REEL_WIDE; + } + return FMTS; + } + if (kind == KIND_STILL) { + if (ch == CH_INSTAGRAM) { + return FMT_PORTRAIT; + } + if (ch == CH_LINKEDIN || ch == CH_TELEGRAM) { + return FMT_SQUARE; + } + if (ch == CH_X) { + return FMT_X_CARD; + } + return FMTS; + } + if (kind == KIND_CAROUSEL) { + if (ch == CH_INSTAGRAM || ch == CH_LINKEDIN) { + return FMT_CAROUSEL; + } + return FMTS; + } + if (kind == KIND_COVER) { + if (ch == CH_INSTAGRAM || ch == CH_TIKTOK || ch == CH_YT_SHORTS) { + return FMT_COVER; + } + return FMTS; + } + if (kind == KIND_LINK) { + if (ch == CH_X) { + return FMT_X_CARD; + } + if (ch == CH_LINKEDIN || ch == CH_TELEGRAM || ch == CH_SITE) { + return FMT_OG; + } + return FMTS; + } + return FMTS; +} + +// How many images the pair asks the renderer for on lesson t: the factory's slide count for a +// carousel, one for every other format, none for a pair with no format. +pub fn images_for(ch: usize, kind: usize, t: usize) -> usize { + const f = pick_format(ch, kind); + if (f == FMTS) { + return 0; + } + if (f == FMT_CAROUSEL) { + return carousel_slides(t); + } + return 1; +} + +// ---- scene: which archetype an item gets ---- + +// ARCHS means "no plan": the item is outside what the factory plans. +pub fn scene_for(t: usize) -> usize { + if (t >= TOPICS) { + return ARCHS; + } + return arche(t); +} + +// The scene shows content the item has, so the picture is of that item. +pub fn scene_backed(t: usize) -> bool { + const a = scene_for(t); + if (a >= ARCHS) { + return false; + } + return content(t, a) > 0; +} + +// ---- gate: may this set of stills go on ---- + +pub const G_PASS : usize = 0; +pub const G_TOO_ALIKE : usize = 1; // some pair is above the bound +pub const G_INCOMPLETE : usize = 2; // fewer (or more) pairs were measured than the set has +pub const G_NOT_MEASURED : usize = 3; // the comparison did not run, or its answer is not an SSIM +pub const G_EMPTY : usize = 4; // nothing to publish +pub const SSIM_ONE : u32 = 1000; // identical images read 1000 permille + +// The pairs among n stills. +pub fn pairs_of(n: usize) -> usize { + if (n < 2) { + return 0; + } + return n * (n - 1) / 2; +} + +// items: how many stills the run holds. ran: whether the comparison ran at all. pairs: how many +// pairs it measured. worst: the highest SSIM it found, in permille. +pub fn gate_verdict(items: usize, ran: bool, pairs: usize, worst: u32) -> usize { + if (items == 0) { + return G_EMPTY; + } + if (ran == false || worst > SSIM_ONE) { + return G_NOT_MEASURED; + } + if (too_similar(worst)) { + return G_TOO_ALIKE; + } + if (pairs != pairs_of(items)) { + return G_INCOMPLETE; + } + return G_PASS; +} + +// ---- consent: may this post go out ---- + +pub const SRC_OWNER_CHAT : usize = 0; // the owner typed it to the agent in chat +pub const SRC_OTHER : usize = 1; // anything else: a page, a file, a comment, an email, an agent + +pub const P_OK : usize = 0; +pub const P_GATE : usize = 1; // the uniqueness gate did not pass +pub const P_NO_POST : usize = 2; // the post has no digest, so there is nothing to approve +pub const P_NO_YES : usize = 3; // no explicit yes +pub const P_NOT_OWNER : usize = 4; // the yes did not come from the owner's chat +pub const P_OTHER_POST : usize = 5; // the yes was for another post +pub const P_OTHER_CHANNEL : usize = 6; // the yes was for this post on another channel + +// gate: gate_verdict's answer. yes_*: what the owner approved. post, channel: what is about to go. +// A digest of 0 means "not computed" and approves nothing. +pub fn publish_verdict(gate: usize, yes: bool, source: usize, yes_post: u32, yes_channel: usize, post: u32, channel: usize) -> usize { + if (gate != G_PASS) { + return P_GATE; + } + if (post == 0) { + return P_NO_POST; + } + if (yes == false) { + return P_NO_YES; + } + if (source != SRC_OWNER_CHAT) { + return P_NOT_OWNER; + } + if (yes_post != post) { + return P_OTHER_POST; + } + if (yes_channel != channel) { + return P_OTHER_CHANNEL; + } + return P_OK; +} + +pub fn may_publish(verdict: usize) -> bool { + return verdict == P_OK; +} + +// ---- tests: format ---- + +test "a moving kind gets a moving format and no other kind does" { + var ch : usize = 0; + while (ch < CHANNELS) { + var k : usize = 0; + while (k < KINDS) { + const f = pick_format(ch, k); + assert(f <= FMTS); + if (f < FMTS) { + assert((f == FMT_REEL_TALL || f == FMT_REEL_WIDE) == (k == KIND_MOVING)); + } + k = k + 1; + } + ch = ch + 1; + } +} + +test "the pairs the law knows get the sizes the factory documents" { + assert(pick_format(CH_INSTAGRAM, KIND_MOVING) == FMT_REEL_TALL); + assert(pick_format(CH_TIKTOK, KIND_MOVING) == FMT_REEL_TALL); + assert(pick_format(CH_YT_SHORTS, KIND_MOVING) == FMT_REEL_TALL); + assert(pick_format(CH_YOUTUBE, KIND_MOVING) == FMT_REEL_WIDE); + assert(pick_format(CH_INSTAGRAM, KIND_STILL) == FMT_PORTRAIT); + assert(pick_format(CH_LINKEDIN, KIND_STILL) == FMT_SQUARE); + assert(pick_format(CH_X, KIND_STILL) == FMT_X_CARD); + assert(pick_format(CH_X, KIND_LINK) == FMT_X_CARD); + assert(pick_format(CH_SITE, KIND_LINK) == FMT_OG); + assert(pick_format(CH_INSTAGRAM, KIND_CAROUSEL) == FMT_CAROUSEL); + assert(pick_format(CH_TIKTOK, KIND_COVER) == FMT_COVER); +} + +test "a pair with no format is unsupported, not guessed" { + assert(pick_format(CH_SITE, KIND_MOVING) == FMTS); + assert(pick_format(CH_YOUTUBE, KIND_STILL) == FMTS); + assert(pick_format(CH_TIKTOK, KIND_CAROUSEL) == FMTS); + assert(pick_format(CH_X, KIND_CAROUSEL) == FMTS); + assert(pick_format(CH_YOUTUBE, KIND_COVER) == FMTS); + assert(pick_format(CHANNELS, KIND_MOVING) == FMTS); + assert(pick_format(CH_INSTAGRAM, KINDS) == FMTS); + assert(images_for(CH_SITE, KIND_MOVING, 3) == 0); +} + +test "every channel can be asked for at least one thing and a carousel is the factory's count" { + var ch : usize = 0; + while (ch < CHANNELS) { + var any = false; + var k : usize = 0; + while (k < KINDS) { + if (pick_format(ch, k) < FMTS) { + any = true; + } + k = k + 1; + } + assert(any); + ch = ch + 1; + } + var t : usize = 0; + while (t < TOPICS) { + assert(images_for(CH_INSTAGRAM, KIND_CAROUSEL, t) == carousel_slides(t)); + assert(images_for(CH_INSTAGRAM, KIND_CAROUSEL, t) >= 3); + assert(images_for(CH_INSTAGRAM, KIND_STILL, t) == 1); + t = t + 1; + } +} + +// ---- tests: scene ---- + +test "the scene is the factory's, for every lesson, and shows content the lesson has" { + var t : usize = 0; + while (t < TOPICS) { + assert(scene_for(t) == arche(t)); + assert(scene_for(t) < ARCHS); + assert(scene_backed(t)); + t = t + 1; + } +} + +test "an item outside the course gets no scene" { + assert(scene_for(TOPICS) == ARCHS); + assert(scene_for(TOPICS + 1000) == ARCHS); + assert(scene_backed(TOPICS) == false); +} + +test "two lessons far apart in the course do not get the same scene as their neighbours" { + assert(scene_for(1) != scene_for(14)); + var t : usize = 1; + while (t < TOPICS) { + assert(scene_for(t) != scene_for(t - 1)); + t = t + 1; + } +} + +// ---- tests: gate ---- + +test "a complete measured set under the bound passes" { + assert(gate_verdict(27, true, 351, 400) == G_PASS); + assert(gate_verdict(27, true, 351, 850) == G_PASS); + assert(gate_verdict(2, true, 1, 0) == G_PASS); + assert(gate_verdict(1, true, 0, 0) == G_PASS); + assert(pairs_of(27) == 351); + assert(pairs_of(0) == 0); + assert(pairs_of(1) == 0); +} + +test "a pair above the bound blocks, a pair at the bound does not" { + assert(gate_verdict(27, true, 351, 851) == G_TOO_ALIKE); + assert(gate_verdict(27, true, 351, 1000) == G_TOO_ALIKE); + assert(gate_verdict(27, true, 351, 850) == G_PASS); +} + +test "an empty, unmeasured, partial or impossible run never passes" { + assert(gate_verdict(0, true, 0, 0) == G_EMPTY); + assert(gate_verdict(27, false, 351, 400) == G_NOT_MEASURED); + assert(gate_verdict(27, true, 351, 1001) == G_NOT_MEASURED); + assert(gate_verdict(27, true, 350, 400) == G_INCOMPLETE); + assert(gate_verdict(27, true, 352, 400) == G_INCOMPLETE); + assert(gate_verdict(27, true, 0, 0) == G_INCOMPLETE); + assert(gate_verdict(2, true, 0, 0) == G_INCOMPLETE); +} + +// ---- tests: consent ---- + +test "a post goes out only with a passed gate and the owner's yes for it" { + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 7, CH_X, 7, CH_X) == P_OK); + assert(may_publish(P_OK)); +} + +test "every missing piece blocks, in the order the owner would ask" { + assert(publish_verdict(G_TOO_ALIKE, true, SRC_OWNER_CHAT, 7, CH_X, 7, CH_X) == P_GATE); + assert(publish_verdict(G_NOT_MEASURED, true, SRC_OWNER_CHAT, 7, CH_X, 7, CH_X) == P_GATE); + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 0, CH_X, 0, CH_X) == P_NO_POST); + assert(publish_verdict(G_PASS, false, SRC_OWNER_CHAT, 7, CH_X, 7, CH_X) == P_NO_YES); + assert(publish_verdict(G_PASS, true, SRC_OTHER, 7, CH_X, 7, CH_X) == P_NOT_OWNER); + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 8, CH_X, 7, CH_X) == P_OTHER_POST); + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 7, CH_X, 7, CH_LINKEDIN) == P_OTHER_CHANNEL); + assert(may_publish(P_GATE) == false); + assert(may_publish(P_NO_YES) == false); + assert(may_publish(P_OTHER_POST) == false); +} + +test "a yes does not carry over: another post, another channel, a default digest" { + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 7, CH_X, 9, CH_X) != P_OK); + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 7, CH_X, 7, CH_TELEGRAM) != P_OK); + assert(publish_verdict(G_PASS, true, SRC_OWNER_CHAT, 0, CH_INSTAGRAM, 0, CH_INSTAGRAM) != P_OK); +} + +test "no combination of the other inputs lets a failed gate through" { + var g : usize = 1; + while (g <= G_EMPTY) { + var s : usize = 0; + while (s <= SRC_OTHER) { + assert(may_publish(publish_verdict(g, true, s, 7, CH_X, 7, CH_X)) == false); + s = s + 1; + } + g = g + 1; + } +} diff --git a/specs/skills/t27-social-visual-consent.t27 b/specs/skills/t27-social-visual-consent.t27 new file mode 100644 index 0000000000..1b83b946a5 --- /dev/null +++ b/specs/skills/t27-social-visual-consent.t27 @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-social-visual-consent.t27 -- skill t27/social-visual-consent +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/social-visual-consent). +; Decision 4 of 4: may this post go out; passed gate and the owner's yes for this post and channel. +; The rules live once, in specs/claw/social_visuals.t27, which imports specs/t27f/reel_factory.t27; +; this card points at them and copies none. There is no SKILL.md behind it yet, so the card is +; spec-only (see "Witness labels" in specs/skills/README.md). Issue gHashTag/t27#10570, epic +; gHashTag/t27#10560. +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_social_visual_consent; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/social-visual-consent"; +pub const NAME : str = "Social Visual Consent"; +pub const REPO : str = "t27"; +; No SKILL.md exists for this card. +pub const SOURCE : str = ""; +pub const SUMMARY_EN : str = "Publish a social post only with the owner's explicit yes, given in the owner's own chat, for that exact post on that channel after the uniqueness gate passed; no standing yes, no default match"; +pub const COMMAND : str = "/social-visual-consent"; +; Specs this skill stands on: the decision law, then the factory it reads. +pub const SPECS : [2]str = ["specs/claw/social_visuals.t27", "specs/t27f/reel_factory.t27"]; +pub const TAGS : [4]str = ["social-visual", "consent", "publish", "claw"]; +pub const ENABLED : bool = true; +; Default budget; no SKILL.md declares a timeout. +pub const TIMEOUT_MIN : u16 = 30; + +test basic_constants { + assert KIND == "skill"; + assert ID == "t27/social-visual-consent"; + assert NAME == "Social Visual Consent"; + assert REPO == "t27"; + assert SOURCE == ""; + assert COMMAND == "/social-visual-consent"; + assert ENABLED == true; + assert TIMEOUT_MIN == 30; + assert TAGS.len == 4; + assert TAGS[0] == "social-visual"; + assert SPECS.len == 2; + assert SPECS[0] == "specs/claw/social_visuals.t27"; + assert SPECS[1] == "specs/t27f/reel_factory.t27"; +} diff --git a/specs/skills/t27-social-visual-format.t27 b/specs/skills/t27-social-visual-format.t27 new file mode 100644 index 0000000000..3b6e8a38e6 --- /dev/null +++ b/specs/skills/t27-social-visual-format.t27 @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-social-visual-format.t27 -- skill t27/social-visual-format +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/social-visual-format). +; Decision 1 of 4: which of reel_factory's eight output formats a (channel, kind) pair gets. +; The rules live once, in specs/claw/social_visuals.t27, which imports specs/t27f/reel_factory.t27; +; this card points at them and copies none. There is no SKILL.md behind it yet, so the card is +; spec-only (see "Witness labels" in specs/skills/README.md). Issue gHashTag/t27#10570, epic +; gHashTag/t27#10560. +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_social_visual_format; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/social-visual-format"; +pub const NAME : str = "Social Visual Format"; +pub const REPO : str = "t27"; +; No SKILL.md exists for this card. +pub const SOURCE : str = ""; +pub const SUMMARY_EN : str = "Pick the output size for a social channel and kind of post (reel, still, carousel, cover, link card) from the content factory's formats, and answer unsupported for a pair with no format instead of guessing"; +pub const COMMAND : str = "/social-visual-format"; +; Specs this skill stands on: the decision law, then the factory it reads. +pub const SPECS : [2]str = ["specs/claw/social_visuals.t27", "specs/t27f/reel_factory.t27"]; +pub const TAGS : [4]str = ["social-visual", "format", "t27f", "claw"]; +pub const ENABLED : bool = true; +; Default budget; no SKILL.md declares a timeout. +pub const TIMEOUT_MIN : u16 = 30; + +test basic_constants { + assert KIND == "skill"; + assert ID == "t27/social-visual-format"; + assert NAME == "Social Visual Format"; + assert REPO == "t27"; + assert SOURCE == ""; + assert COMMAND == "/social-visual-format"; + assert ENABLED == true; + assert TIMEOUT_MIN == 30; + assert TAGS.len == 4; + assert TAGS[0] == "social-visual"; + assert SPECS.len == 2; + assert SPECS[0] == "specs/claw/social_visuals.t27"; + assert SPECS[1] == "specs/t27f/reel_factory.t27"; +} diff --git a/specs/skills/t27-social-visual-gate.t27 b/specs/skills/t27-social-visual-gate.t27 new file mode 100644 index 0000000000..9753850699 --- /dev/null +++ b/specs/skills/t27-social-visual-gate.t27 @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-social-visual-gate.t27 -- skill t27/social-visual-gate +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/social-visual-gate). +; Decision 3 of 4: may this set of stills go on, from item count, pairs measured and worst SSIM. +; The rules live once, in specs/claw/social_visuals.t27, which imports specs/t27f/reel_factory.t27; +; this card points at them and copies none. There is no SKILL.md behind it yet, so the card is +; spec-only (see "Witness labels" in specs/skills/README.md). Issue gHashTag/t27#10570, epic +; gHashTag/t27#10560. +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_social_visual_gate; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/social-visual-gate"; +pub const NAME : str = "Social Visual Gate"; +pub const REPO : str = "t27"; +; No SKILL.md exists for this card. +pub const SOURCE : str = ""; +pub const SUMMARY_EN : str = "Before anything is published, check that the set of stills is complete, measured and unique: block an empty, unmeasured, incomplete or too-alike set using the factory's similarity bound"; +pub const COMMAND : str = "/social-visual-gate"; +; Specs this skill stands on: the decision law, then the factory it reads. +pub const SPECS : [2]str = ["specs/claw/social_visuals.t27", "specs/t27f/reel_factory.t27"]; +pub const TAGS : [4]str = ["social-visual", "gate", "t27f", "claw"]; +pub const ENABLED : bool = true; +; Default budget; no SKILL.md declares a timeout. +pub const TIMEOUT_MIN : u16 = 30; + +test basic_constants { + assert KIND == "skill"; + assert ID == "t27/social-visual-gate"; + assert NAME == "Social Visual Gate"; + assert REPO == "t27"; + assert SOURCE == ""; + assert COMMAND == "/social-visual-gate"; + assert ENABLED == true; + assert TIMEOUT_MIN == 30; + assert TAGS.len == 4; + assert TAGS[0] == "social-visual"; + assert SPECS.len == 2; + assert SPECS[0] == "specs/claw/social_visuals.t27"; + assert SPECS[1] == "specs/t27f/reel_factory.t27"; +} diff --git a/specs/skills/t27-social-visual-scene.t27 b/specs/skills/t27-social-visual-scene.t27 new file mode 100644 index 0000000000..2d8415bb9c --- /dev/null +++ b/specs/skills/t27-social-visual-scene.t27 @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-social-visual-scene.t27 -- skill t27/social-visual-scene +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/social-visual-scene). +; Decision 2 of 4: which scene archetype an item gets, always reel_factory::arche, never a copy of it. +; The rules live once, in specs/claw/social_visuals.t27, which imports specs/t27f/reel_factory.t27; +; this card points at them and copies none. There is no SKILL.md behind it yet, so the card is +; spec-only (see "Witness labels" in specs/skills/README.md). Issue gHashTag/t27#10570, epic +; gHashTag/t27#10560. +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_social_visual_scene; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/social-visual-scene"; +pub const NAME : str = "Social Visual Scene"; +pub const REPO : str = "t27"; +; No SKILL.md exists for this card. +pub const SOURCE : str = ""; +pub const SUMMARY_EN : str = "Pick the scene archetype for an item from its own content by calling the content factory, and refuse an item the factory has no plan for instead of inventing a scene"; +pub const COMMAND : str = "/social-visual-scene"; +; Specs this skill stands on: the decision law, then the factory it reads. +pub const SPECS : [2]str = ["specs/claw/social_visuals.t27", "specs/t27f/reel_factory.t27"]; +pub const TAGS : [4]str = ["social-visual", "scene", "t27f", "claw"]; +pub const ENABLED : bool = true; +; Default budget; no SKILL.md declares a timeout. +pub const TIMEOUT_MIN : u16 = 30; + +test basic_constants { + assert KIND == "skill"; + assert ID == "t27/social-visual-scene"; + assert NAME == "Social Visual Scene"; + assert REPO == "t27"; + assert SOURCE == ""; + assert COMMAND == "/social-visual-scene"; + assert ENABLED == true; + assert TIMEOUT_MIN == 30; + assert TAGS.len == 4; + assert TAGS[0] == "social-visual"; + assert SPECS.len == 2; + assert SPECS[0] == "specs/claw/social_visuals.t27"; + assert SPECS[1] == "specs/t27f/reel_factory.t27"; +}