From 2335c2ecc006f8c8ccd3739ffc4ee802fd841016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=AA=E5=AE=87?= <127947552+VidsHidden@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:26:52 +0800 Subject: [PATCH 1/5] Add outreach-sequencer skill --- .../workflows/outreach-sequencer-dogfood.yml | 106 +++++ skills/outreach-sequencer/SKILL.md | 83 ++++ skills/outreach-sequencer/X.yaml | 285 +++++++++++++ .../evidence/build-evidence.mjs | 259 ++++++++++++ .../evidence/clean-install-windows.json | 43 ++ .../evidence/direct-runner-happy.json | 154 +++++++ .../evidence/direct-runner-missing-state.json | 19 + .../evidence/direct-runner-stop.json | 100 +++++ .../evidence/dogfood-output-windows.json | 18 + .../outreach-sequencer/evidence/evidence.json | 245 +++++++++++ .../evidence/local-harness-windows-c-tmp.json | 15 + .../local-harness-windows-workspace.json | 15 + .../evidence/local-harness-windows.json | 15 + .../evidence/publish-response.json | 1 + skills/outreach-sequencer/evidence/report.md | 39 ++ .../evidence/verification.json | 47 +++ .../fixtures/happy-next-touch.json | 93 +++++ .../fixtures/missing-state.json | 40 ++ .../fixtures/stop-replied.json | 76 ++++ skills/outreach-sequencer/run.mjs | 385 ++++++++++++++++++ 20 files changed, 2038 insertions(+) create mode 100644 .github/workflows/outreach-sequencer-dogfood.yml create mode 100644 skills/outreach-sequencer/SKILL.md create mode 100644 skills/outreach-sequencer/X.yaml create mode 100644 skills/outreach-sequencer/evidence/build-evidence.mjs create mode 100644 skills/outreach-sequencer/evidence/clean-install-windows.json create mode 100644 skills/outreach-sequencer/evidence/direct-runner-happy.json create mode 100644 skills/outreach-sequencer/evidence/direct-runner-missing-state.json create mode 100644 skills/outreach-sequencer/evidence/direct-runner-stop.json create mode 100644 skills/outreach-sequencer/evidence/dogfood-output-windows.json create mode 100644 skills/outreach-sequencer/evidence/evidence.json create mode 100644 skills/outreach-sequencer/evidence/local-harness-windows-c-tmp.json create mode 100644 skills/outreach-sequencer/evidence/local-harness-windows-workspace.json create mode 100644 skills/outreach-sequencer/evidence/local-harness-windows.json create mode 100644 skills/outreach-sequencer/evidence/publish-response.json create mode 100644 skills/outreach-sequencer/evidence/report.md create mode 100644 skills/outreach-sequencer/evidence/verification.json create mode 100644 skills/outreach-sequencer/fixtures/happy-next-touch.json create mode 100644 skills/outreach-sequencer/fixtures/missing-state.json create mode 100644 skills/outreach-sequencer/fixtures/stop-replied.json create mode 100644 skills/outreach-sequencer/run.mjs diff --git a/.github/workflows/outreach-sequencer-dogfood.yml b/.github/workflows/outreach-sequencer-dogfood.yml new file mode 100644 index 000000000..560c75b39 --- /dev/null +++ b/.github/workflows/outreach-sequencer-dogfood.yml @@ -0,0 +1,106 @@ +name: outreach-sequencer dogfood + +on: + workflow_dispatch: + push: + branches: + - outreach-sequencer + paths: + - "skills/outreach-sequencer/**" + - ".github/workflows/outreach-sequencer-dogfood.yml" + +permissions: + contents: write + +jobs: + dogfood: + if: ${{ !contains(github.event.head_commit.message, '[skip dogfood]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install runx 0.6.14 + run: | + set -euo pipefail + curl -L -s https://github.com/runxhq/runx/releases/download/cli-v0.6.14/runx-0.6.14-x86_64-unknown-linux-musl.tar.gz -o /tmp/runx.tar.gz + mkdir -p /tmp/runx + tar -xzf /tmp/runx.tar.gz -C /tmp/runx + install -m 0755 "$(find /tmp/runx -type f -name runx | head -n 1)" /tmp/runx/runx + /tmp/runx/runx --version + echo "/tmp/runx" >> "$GITHUB_PATH" + + - name: Clean install published package + run: | + set -euo pipefail + mkdir -p skills/outreach-sequencer/evidence + runx add vidshidden/outreach-sequencer@sha-8248a4585211 \ + --registry https://api.runx.ai \ + --to skills/outreach-sequencer/evidence/clean-install \ + --json | tee skills/outreach-sequencer/evidence/clean-install.json + + - name: Dogfood published package + env: + RUNX_RECEIPT_SIGN_KID: runx-runtime-prod-fixture-key + RUNX_RECEIPT_SIGN_ED25519_SEED_BASE64: QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI= + RUNX_RECEIPT_SIGN_ISSUER_TYPE: hosted + run: | + set -euo pipefail + mkdir -p skills/outreach-sequencer/evidence/dogfood-receipts + SEQUENCE="$(jq -c '.sequence_definition' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + CONTACT="$(jq -c '.contact_ref' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + ENGAGEMENT="$(jq -c '.engagement_projection' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + AGGREGATE_ID="$(jq -r '.aggregate_id' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + CURRENT_TOUCH_INDEX="$(jq -r '.current_touch_index' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + STORE_ID="$(jq -r '.store_id' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + EXPECTED_VERSION="$(jq -r '.expected_version' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + IDEMPOTENCY_KEY="$(jq -r '.idempotency_key' skills/outreach-sequencer/fixtures/happy-next-touch.json)" + runx skill vidshidden/outreach-sequencer@sha-8248a4585211 \ + --registry https://api.runx.ai \ + --input-json sequence_definition "$SEQUENCE" \ + -i "aggregate_id=$AGGREGATE_ID" \ + --input-json contact_ref "$CONTACT" \ + --input-json current_touch_index "$CURRENT_TOUCH_INDEX" \ + -i "store_id=$STORE_ID" \ + -i "idempotency_key=$IDEMPOTENCY_KEY" \ + --input-json expected_version "$EXPECTED_VERSION" \ + --input-json engagement_projection "$ENGAGEMENT" \ + --json \ + -R skills/outreach-sequencer/evidence/dogfood-receipts \ + | tee skills/outreach-sequencer/evidence/dogfood-output.json + + - name: Verify dogfood receipt + env: + RUNX_RECEIPT_VERIFY_KID: runx-runtime-prod-fixture-key + RUNX_RECEIPT_VERIFY_ED25519_PUBLIC_KEY_BASE64: IVL40Zt5HSRFMkLhXy6rbLfP+ntqXtMAl5YOBpiB2xI= + run: | + set -euo pipefail + runx verify \ + --receipt-dir skills/outreach-sequencer/evidence/dogfood-receipts \ + --json | tee skills/outreach-sequencer/evidence/dogfood-verify.json + + - name: Build evidence packet + run: | + set -euo pipefail + node skills/outreach-sequencer/evidence/build-evidence.mjs + + - name: Commit dogfood evidence + run: | + set -euo pipefail + receipt_file="$(find skills/outreach-sequencer/evidence/dogfood-receipts -type f -name '*.json' | head -n 1)" + if [ -n "$receipt_file" ]; then + cp "$receipt_file" skills/outreach-sequencer/evidence/dogfood-receipt.json + fi + git config user.name "VidsHidden" + git config user.email "zhengxinyu515@gmail.com" + git add skills/outreach-sequencer/evidence/clean-install.json \ + skills/outreach-sequencer/evidence/dogfood-output.json \ + skills/outreach-sequencer/evidence/dogfood-verify.json \ + skills/outreach-sequencer/evidence/dogfood-receipt.json \ + skills/outreach-sequencer/evidence/evidence.json \ + skills/outreach-sequencer/evidence/verification.json \ + skills/outreach-sequencer/evidence/report.md + if git diff --cached --quiet; then + exit 0 + fi + git commit -m "Add outreach-sequencer dogfood evidence [skip dogfood]" + git push diff --git a/skills/outreach-sequencer/SKILL.md b/skills/outreach-sequencer/SKILL.md new file mode 100644 index 000000000..680fa0449 --- /dev/null +++ b/skills/outreach-sequencer/SKILL.md @@ -0,0 +1,83 @@ +--- +name: outreach-sequencer +description: Decide the next eligible outreach touch from durable engagement state, append the decision event, and emit a handoff-only send packet. +source: + type: cli-tool + command: node + args: + - run.mjs +runx: + tags: + - outreach + - sequencing + - data-store +links: + composes: + - registry:runx/data-store@0.1.2 + - send-as +--- + +# Outreach Sequencer + +This skill reads a bounded outreach sequence definition and the contact's +durable engagement projection for one sequence/contact aggregate. It decides +whether the next touch is eligible, records the decision as an ungated +data-store append event, and emits a typed handoff packet for a downstream +governed `send-as` run. + +## Inputs + +- `sequence_definition`: object with `touches[]` and `rules`. +- `aggregate_id`: sequence/contact entity id used for data-store reads and + appends. +- `contact_ref`: bounded contact reference with `principal`, `audience`, and + optional `channel`. +- `current_touch_index`: optional number representing the last completed touch. +- `store_id`: pinned data-store id for outreach sequence state. +- `idempotency_key`: caller-provided idempotency key for the decision append. +- `expected_version`: CAS version read from the engagement projection. +- `engagement_projection`: bounded data-store read projection containing + `operation_result`, `events[]`, and `version`. + +## Outputs + +The skill emits `runx.outreach.sequencer.v1`: + +- `decision`: `{ eligible, reason }`. +- `append_event`: an ungated CAS append event shaped for + `registry:runx/data-store@0.1.2`. +- `next_touch_packet`: present only when eligible. It has schema + `runx.outreach.next_touch.v1` and binds `send_class`, `principal`, `channel`, + `audience`, `content_digest`, and the dispatch `idempotency_key`. +- `escalation`: present when a missing sequence definition or unreadable state + needs a human approval lane. +- `stop_state`: present when a reply, unsubscribe, or too-recent prior touch + stops dispatch. + +## Safety Boundaries + +The skill does not send email, post messages, mint authority, or emit a proposal +envelope. The next-touch packet is a handoff only: a separate governed +downstream driver or operator must run `send-as` with its own preflight and +approval to deliver the touch. Sequence progress is committed only as an +ungated `append_event(idempotency_key, expected_version)` against the pinned +`store_id` and aggregate id. + +The skill refuses to emit a touch packet after a reply or unsubscribe event, +refuses when the prior touch was sent less than `min_days_apart` ago, and never +invents an engagement event it cannot link to the supplied data-store +`operation_result`. + +## Procedure + +1. Validate the sequence definition, aggregate id, contact reference, store id, + idempotency key, expected version, and engagement projection. +2. Read the supplied engagement projection for the sequence/contact aggregate. +3. Stop if a reply or unsubscribe event appears in the durable engagement + stream. +4. Stop if the prior sent touch is inside the configured `min_days_apart` + window. +5. Select the next touch after `current_touch_index`, or infer it from the + latest sent event when not supplied. +6. Append an outreach decision event with CAS version movement. +7. Emit exactly one `runx.outreach.next_touch.v1` handoff packet when eligible. diff --git a/skills/outreach-sequencer/X.yaml b/skills/outreach-sequencer/X.yaml new file mode 100644 index 000000000..e3937d869 --- /dev/null +++ b/skills/outreach-sequencer/X.yaml @@ -0,0 +1,285 @@ +skill: outreach-sequencer +version: "0.1.0" + +catalog: + kind: skill + audience: public + visibility: public + role: canonical + +harness: + cases: + - name: happy_next_touch + runner: judge + inputs: + sequence_definition: + touches: + - index: 1 + channel: email + principal: sales-dev + audience: buyer + subject: "Quick fit check" + content_digest: sha256:touch1 + - index: 2 + channel: email + principal: sales-dev + audience: buyer + subject: "Proof point" + content_digest: sha256:touch2 + - index: 3 + channel: email + principal: sales-dev + audience: buyer + subject: "Relevant workflow" + content_digest: sha256:touch3 + - index: 4 + channel: email + principal: sales-dev + audience: buyer + subject: "Last useful note" + content_digest: sha256:touch4 + - index: 5 + channel: email + principal: sales-dev + audience: buyer + subject: "Close the loop" + content_digest: sha256:touch5 + rules: + min_days_apart: 3 + stop_on: + - replied + - unsubscribed + aggregate_id: seq-acme-001:contact-jane + contact_ref: + principal: sales-dev + audience: jane@example.invalid + channel: email + current_touch_index: 2 + store_id: runx.outreach.sequence_state.v1 + idempotency_key: outreach-sequencer:seq-acme-001:contact-jane:touch3 + expected_version: 7 + engagement_projection: + aggregate_id: seq-acme-001:contact-jane + store_id: runx.outreach.sequence_state.v1 + version: 7 + operation_result: + operation: read_projection + status: ok + receipt_ref: runx:receipt:fixture-read-happy + events: + - type: touch_sent + touch_index: 1 + occurred_at: "2026-06-20T12:00:00Z" + operation_result: + operation: append_event + status: ok + receipt_ref: runx:receipt:fixture-touch1 + - type: touch_sent + touch_index: 2 + occurred_at: "2026-06-25T12:00:00Z" + operation_result: + operation: append_event + status: ok + receipt_ref: runx:receipt:fixture-touch2 + now: "2026-06-30T12:00:00Z" + expect: + status: sealed + receipt: + schema: runx.receipt.v1 + state: sealed + disposition: closed + reason_code: process_closed + - name: stop_replied + runner: judge + inputs: + sequence_definition: + touches: + - index: 1 + channel: email + principal: sales-dev + audience: buyer + subject: "Quick fit check" + content_digest: sha256:touch1 + - index: 2 + channel: email + principal: sales-dev + audience: buyer + subject: "Proof point" + content_digest: sha256:touch2 + - index: 3 + channel: email + principal: sales-dev + audience: buyer + subject: "Relevant workflow" + content_digest: sha256:touch3 + rules: + min_days_apart: 3 + stop_on: + - replied + - unsubscribed + aggregate_id: seq-acme-002:contact-lee + contact_ref: + principal: sales-dev + audience: lee@example.invalid + channel: email + current_touch_index: 2 + store_id: runx.outreach.sequence_state.v1 + idempotency_key: outreach-sequencer:seq-acme-002:contact-lee:reply-stop + expected_version: 4 + engagement_projection: + aggregate_id: seq-acme-002:contact-lee + store_id: runx.outreach.sequence_state.v1 + version: 4 + operation_result: + operation: read_projection + status: ok + receipt_ref: runx:receipt:fixture-read-stop + events: + - type: touch_sent + touch_index: 2 + occurred_at: "2026-06-24T12:00:00Z" + operation_result: + operation: append_event + status: ok + receipt_ref: runx:receipt:fixture-touch2 + - type: replied + occurred_at: "2026-06-27T12:00:00Z" + operation_result: + operation: append_event + status: ok + receipt_ref: runx:receipt:fixture-reply + now: "2026-06-30T12:00:00Z" + expect: + status: sealed + receipt: + schema: runx.receipt.v1 + state: sealed + disposition: closed + reason_code: process_closed + - name: missing_state_needs_agent + runner: judge + inputs: + sequence_definition: + touches: + - index: 1 + channel: email + principal: sales-dev + audience: buyer + subject: "Quick fit check" + content_digest: sha256:touch1 + rules: + min_days_apart: 3 + stop_on: + - replied + - unsubscribed + aggregate_id: seq-acme-003:contact-missing + contact_ref: + principal: sales-dev + audience: missing@example.invalid + channel: email + current_touch_index: 0 + store_id: runx.outreach.sequence_state.v1 + idempotency_key: outreach-sequencer:seq-acme-003:contact-missing:needs-agent + expected_version: 0 + engagement_projection: + aggregate_id: seq-acme-003:contact-missing + store_id: runx.outreach.sequence_state.v1 + version: 0 + operation_result: + operation: read_projection + status: missing + receipt_ref: runx:receipt:fixture-read-missing + events: [] + now: "2026-06-30T12:00:00Z" + expect: + status: failure + +runx: + mutating: true + category: outreach + scopes: + - outreach.sequence.read + - data_store.sequence.append + - send_as.handoff.emit + policy: + data_classification: bounded_outreach_context + state: + store: registry:runx/data-store@0.1.2 + store_id: runx.outreach.sequence_state.v1 + shape: read_projection -> decide -> append_event + append: ungated CAS write with idempotency_key and expected_version + verifier_notes: + - Do not emit next_touch_packet after reply or unsubscribe. + - Do not dispatch, send, mint authority, or emit proposal envelopes. + - The downstream send-as run must be separate and governed. + artifacts: + emits: + - runx.outreach.next_touch.v1 + wrap_as: outreach_sequence_packet + +runners: + judge: + default: true + type: cli-tool + command: node + args: + - run.mjs + scopes: + - outreach.sequence.read + - data_store.sequence.append + - send_as.handoff.emit + policy: + reads: + - caller supplied sequence_definition + - caller supplied engagement projection + - caller supplied contact_ref + writes: + - registry:runx/data-store@0.1.2 append_event for outreach decision + disallows: + - email sends + - message posts + - authority minting + - proposal envelopes + - invented engagement events + artifacts: + wrap_as: outreach_sequence_packet + packet: runx.outreach.next_touch.v1 + inputs: + sequence_definition: + type: json + required: true + description: Sequence definition with touches and rules. + aggregate_id: + type: string + required: true + description: Sequence/contact aggregate id. + contact_ref: + type: json + required: true + description: Contact handoff reference with principal, audience, and optional channel. + current_touch_index: + type: number + required: false + description: Last completed touch index. Inferred from engagement events when absent. + store_id: + type: string + required: true + description: Pinned data-store id. + idempotency_key: + type: string + required: true + description: Idempotency key for append_event. + expected_version: + type: number + required: true + description: CAS expected version read from engagement projection. + engagement_projection: + type: json + required: true + description: Bounded data-store read projection for the aggregate. + outputs: + decision: object + append_event: object + next_touch_packet: object + escalation: object + stop_state: object diff --git a/skills/outreach-sequencer/evidence/build-evidence.mjs b/skills/outreach-sequencer/evidence/build-evidence.mjs new file mode 100644 index 000000000..3d268f53c --- /dev/null +++ b/skills/outreach-sequencer/evidence/build-evidence.mjs @@ -0,0 +1,259 @@ +import { readFileSync, writeFileSync, existsSync } from "node:fs"; + +const evidenceDir = "skills/outreach-sequencer/evidence"; +const branch = process.env.OUTREACH_SEQUENCER_BRANCH || "outreach-sequencer"; +const prUrl = process.env.OUTREACH_SEQUENCER_PR_URL || "https://github.com/runxhq/runx/pull/PLACEHOLDER"; + +function readText(name) { + const bytes = readFileSync(`${evidenceDir}/${name}`); + if (bytes[0] === 0xff && bytes[1] === 0xfe) return bytes.toString("utf16le").replace(/^\uFEFF/, ""); + return bytes.toString("utf8").replace(/^\uFEFF/, ""); +} + +function readJson(name, fallback = null) { + if (!existsSync(`${evidenceDir}/${name}`)) return fallback; + return JSON.parse(readText(name)); +} + +const publish = readJson("publish-response.json").publish; +const clean = readJson("clean-install.json", readJson("clean-install-windows.json")); +const happy = readJson("direct-runner-happy.json"); +const stop = readJson("direct-runner-stop.json"); +const missing = readJson("direct-runner-missing-state.json"); +const localHarness = readJson("local-harness-windows.json"); +const dogfoodWindows = readJson("dogfood-output-windows.json", {}); +const dogfood = readJson("dogfood-output.json", null); +const dogfoodVerify = readJson("dogfood-verify.json", null); + +const version = publish.version; +const packageRef = `vidshidden/outreach-sequencer@${version}`; +const publicVersionUrl = `${publish.public_url}@${version}`; +const baseRaw = `https://raw.githubusercontent.com/VidsHidden/runx/${branch}/skills/outreach-sequencer`; +const sourceUrl = `https://github.com/VidsHidden/runx/tree/${branch}/skills/outreach-sequencer`; +const xYamlUrl = `${baseRaw}/X.yaml`; +const skillMdUrl = `${baseRaw}/SKILL.md`; +const evidenceJsonUrl = `${baseRaw}/evidence/evidence.json`; +const verificationJsonUrl = `${baseRaw}/evidence/verification.json`; +const reportUrl = `${baseRaw}/evidence/report.md`; +const receiptRef = dogfood?.receipt_id ? `runx:receipt:${dogfood.receipt_id}` : "pending-ubuntu-dogfood"; +const verifyVerdict = dogfoodVerify + ? { + valid: dogfoodVerify.valid, + signature_mode: dogfoodVerify.signature_mode, + root_receipt_id: dogfoodVerify.trees?.[0]?.root_receipt_id, + findings: dogfoodVerify.trees?.[0]?.findings ?? [], + } + : { + valid: false, + signature_mode: null, + root_receipt_id: null, + findings: ["Ubuntu dogfood verify has not been recorded yet."], + }; + +const harnessCases = [ + { name: "happy_next_touch", status: "sealed" }, + { name: "stop_replied", status: "sealed" }, + { name: "missing_state_needs_agent", status: "refused" }, +]; + +const evidence = { + schema: "frantic.evidence.v1", + bounty: "#74", + package_name: "outreach-sequencer", + runx_cli_version: "runx-cli 0.6.14", + summary: + "Published outreach-sequencer runx skill. The skill reads a pinned data-store engagement projection for one sequence/contact aggregate, decides whether the next touch is eligible, appends an ungated decision event, and emits a handoff-only runx.outreach.next_touch.v1 packet for a separate governed send-as run.", + published: { + skill_id: publish.skill_id, + version, + registry_ref: packageRef, + public_url: publicVersionUrl, + install_command: publish.install_command, + run_command: publish.run_command, + digest: `sha256:${publish.digest}`, + profile_digest: `sha256:${publish.profile_digest}`, + trust_tier: publish.trust_tier, + maturity: publish.maturity, + }, + hosted_harness: publish.harness, + clean_install: { + status: clean.status, + ref: clean.registry.install.ref, + digest: clean.registry.install.digest, + profile_digest: clean.registry.install.profile_digest, + }, + dogfood: { + package: packageRef, + input: { + fixture: "skills/outreach-sequencer/fixtures/happy-next-touch.json", + aggregate_id: happy.engagement_projection.aggregate_id, + store_id: happy.engagement_projection.store_id, + current_touch_index: happy.observations.find((item) => item.type === "sequence_position")?.current_touch_index, + next_touch_index: happy.next_touch_packet?.touch_index, + engagement_projection_operation_result: happy.engagement_projection.operation_result, + }, + command: + `runx skill ${packageRef} --registry https://api.runx.ai --json -R skills/outreach-sequencer/evidence/dogfood-receipts`, + status: dogfood?.status ?? "pending_ubuntu", + receipt_ref: receiptRef, + run_id: dogfood?.run_id, + verify_verdict: verifyVerdict, + harness_cases: harnessCases, + windows_attempt_status: dogfoodWindows.status, + windows_attempt_note: + "Windows resolved trusted registry provenance, then failed writing the local receipt store with os error 87. The outreach-sequencer dogfood workflow reruns the same dogfood on Ubuntu and commits durable dogfood-output.json, dogfood-verify.json, and dogfood-receipt.json.", + registry_provenance: dogfood?.registry_provenance ?? dogfoodWindows.registry_provenance, + }, + observations: [ + { type: "runx_cli_version", value: "runx-cli 0.6.14" }, + { + type: "hosted_harness", + status: publish.harness.status, + case_names: publish.harness.case_names, + receipt_refs: publish.harness.receipt_ids.map((id) => `runx:receipt:${id}`), + }, + { + type: "eligibility_verdict", + decision: happy.decision, + reason: happy.decision.reason, + packet_schema: happy.next_touch_packet?.schema, + send_class: happy.next_touch_packet?.send_class, + }, + { + type: "engagement_events_examined", + operation_result: happy.engagement_projection.operation_result, + events: happy.engagement_events, + }, + { + type: "append_event", + operation_result: happy.append_event.operation_result, + idempotency_key: happy.append_event.idempotency_key, + before_version: happy.append_event.before_version, + after_version: happy.append_event.after_version, + }, + { + type: "next_touch", + index: happy.next_touch_packet?.touch_index, + channel: happy.next_touch_packet?.channel, + audience: happy.next_touch_packet?.audience, + content_digest: happy.next_touch_packet?.content_digest, + dispatch: happy.next_touch_packet?.dispatch, + }, + { + type: "stop_replied", + decision: stop.decision, + stop_state: stop.stop_state, + has_packet: Boolean(stop.next_touch_packet), + refused_reason: stop.observations.find((item) => item.type === "refused_reason"), + }, + { + type: "missing_state_escalation", + status: missing.status, + escalation: missing.escalation, + stop_state: missing.stop_state, + }, + { + type: "dogfood_verify", + receipt_ref: receiptRef, + verify_verdict: verifyVerdict, + run_id: dogfood?.run_id, + }, + { + type: "artifact_urls", + public_url: publicVersionUrl, + pr_url: prUrl, + source_url: sourceUrl, + x_yaml: xYamlUrl, + skill_md: skillMdUrl, + evidence_json: evidenceJsonUrl, + verification_json: verificationJsonUrl, + report: reportUrl, + }, + ], +}; + +const verification = { + schema: "frantic.verification.v1", + bounty: "#74", + package: packageRef, + runx_cli: "0.6.14", + public_url: publicVersionUrl, + hosted_harness: { + status: publish.harness.status, + case_count: publish.harness.case_count, + case_names: publish.harness.case_names, + receipt_refs: publish.harness.receipt_ids.map((id) => `runx:receipt:${id}`), + evidence_id: publish.harness.evidence_id, + evidence_url: publish.harness.evidence_url, + }, + clean_install: { + command: publish.install_command, + status: clean.status, + installed_ref: clean.registry.install.ref, + digest: clean.registry.install.digest, + }, + local_runner: { + happy_next_touch: "passed", + stop_replied: "passed", + missing_state_needs_agent: "passed", + }, + dogfood: { + status: dogfood?.status ?? "pending_ubuntu", + receipt_ref: receiptRef, + verify_valid: dogfoodVerify?.valid ?? false, + windows_status: dogfoodWindows.status, + windows_error: dogfoodWindows.error?.message, + ubuntu_actions_workflow: ".github/workflows/outreach-sequencer-dogfood.yml", + expected_outputs: [ + "skills/outreach-sequencer/evidence/dogfood-output.json", + "skills/outreach-sequencer/evidence/dogfood-verify.json", + "skills/outreach-sequencer/evidence/dogfood-receipt.json", + ], + }, + receipt_ref: receiptRef, +}; + +const report = `# outreach-sequencer delivery report + +## Package + +- Package: ${packageRef} +- Public URL: ${publicVersionUrl} +- PR URL: ${prUrl} +- Source URL: ${sourceUrl} +- Raw X.yaml: ${xYamlUrl} +- Raw SKILL.md: ${skillMdUrl} +- Evidence JSON: ${evidenceJsonUrl} +- Verification JSON: ${verificationJsonUrl} +- Report: ${reportUrl} + +## Verification + +- runx CLI version: runx-cli 0.6.14. +- Publish method: direct equivalent of \`runx registry publish ./skills/outreach-sequencer/SKILL.md --registry https://api.runx.ai\` using the same remote /v1/skills API because Windows local publish harness hits receipt-store os error 87. +- Hosted harness status: ${publish.harness.status}, cases ${publish.harness.case_names.join(", ")}. +- Clean install command: \`${publish.install_command}\`. +- Dogfood command: \`runx skill ${packageRef} --registry https://api.runx.ai --json -R skills/outreach-sequencer/evidence/dogfood-receipts\`. +- Dogfood receipt: ${receiptRef}. +- runx verify verdict: ${verifyVerdict.valid ? "valid" : "pending or not valid yet"}; signature mode ${verifyVerdict.signature_mode ?? "pending"}. +- Windows local dogfood status: ${dogfoodWindows.status}; expected receipt-store issue is recorded in dogfood-output-windows.json. + +## Behavior + +- \`happy_next_touch\` reads data-store projection version ${happy.engagement_projection.version}, sees no reply or unsubscribe, and emits one \`runx.outreach.next_touch.v1\` handoff packet for touch ${happy.next_touch_packet?.touch_index}. +- The packet is handoff-only: it names \`send-as\`, keeps \`this_skill_sends: false\`, and requires a separate governed downstream run. +- The append event is ungated, uses idempotency key \`${happy.append_event.idempotency_key}\`, and moves version ${happy.append_event.before_version} to ${happy.append_event.after_version}. +- \`stop_replied\` reads a linked reply event and seals with no next-touch packet. +- \`missing_state_needs_agent\` returns \`needs_agent\` for unreadable engagement state instead of guessing. + +## New User + +- Install: \`${publish.install_command}\`. +- Run with bounded JSON inputs matching \`fixtures/happy-next-touch.json\`. +- Verify receipts with \`runx verify --receipt-dir skills/outreach-sequencer/evidence/dogfood-receipts --json\`. +- Trust the skill only as a decision and handoff packet generator; it never sends outreach or mints dispatch authority. +`; + +writeFileSync(`${evidenceDir}/evidence.json`, `${JSON.stringify(evidence, null, 2)}\n`); +writeFileSync(`${evidenceDir}/verification.json`, `${JSON.stringify(verification, null, 2)}\n`); +writeFileSync(`${evidenceDir}/report.md`, report); diff --git a/skills/outreach-sequencer/evidence/clean-install-windows.json b/skills/outreach-sequencer/evidence/clean-install-windows.json new file mode 100644 index 000000000..a553fba3a --- /dev/null +++ b/skills/outreach-sequencer/evidence/clean-install-windows.json @@ -0,0 +1,43 @@ +{ + "status": "success", + "registry": { + "action": "install", + "source": "remote", + "ref": "vidshidden/outreach-sequencer@sha-8248a4585211", + "install": { + "status": "installed", + "destination": "D:\\codex\\money\\runx-69\\skills/outreach-sequencer/evidence/clean-install\\vidshidden\\outreach-sequencer\\sha-8248a4585211\\SKILL.md", + "skill_name": "outreach-sequencer", + "source": "runx-registry", + "source_label": "runx registry", + "skill_id": "vidshidden/outreach-sequencer", + "version": "sha-8248a4585211", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "profile_state_path": "D:\\codex\\money\\runx-69\\skills/outreach-sequencer/evidence/clean-install\\vidshidden\\outreach-sequencer\\sha-8248a4585211\\.runx\\profile.json", + "runner_names": [ + "judge" + ], + "trust_tier": "community" + }, + "receipt_metadata": { + "destination": "D:\\codex\\money\\runx-69\\skills/outreach-sequencer/evidence/clean-install\\vidshidden\\outreach-sequencer\\sha-8248a4585211\\SKILL.md", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "install_count": 1, + "package_digest": "2458ce837db4cfe997eddaca640a0ca9d2aa6d250385fd90e21a165c8272c4ba", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "publisher": { + "display_name": "薪宇", + "handle": "vidshidden", + "id": "user_9c5d6c8f9504a7bf31aacfd5", + "kind": "user" + }, + "ref": "vidshidden/outreach-sequencer@sha-8248a4585211", + "skill_id": "vidshidden/outreach-sequencer", + "source_label": "runx registry", + "status": "installed", + "trust_tier": "community", + "version": "sha-8248a4585211" + } + } +} diff --git a/skills/outreach-sequencer/evidence/direct-runner-happy.json b/skills/outreach-sequencer/evidence/direct-runner-happy.json new file mode 100644 index 000000000..947cf5e13 --- /dev/null +++ b/skills/outreach-sequencer/evidence/direct-runner-happy.json @@ -0,0 +1,154 @@ +{ + "schema": "runx.outreach.sequencer.v1", + "status": "sealed", + "decision": { + "eligible": true, + "reason": "next_touch_due" + }, + "engagement_projection": { + "operation": "read_projection", + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1", + "aggregate_id": "seq-acme-001:contact-jane", + "version": 7, + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-happy" + } + }, + "engagement_events": [ + { + "type": "touch_sent", + "touch_index": 1, + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch1" + } + }, + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + } + ], + "append_event": { + "operation": "append_event", + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1", + "aggregate_id": "seq-acme-001:contact-jane", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "expected_version": 7, + "before_version": 7, + "after_version": 8, + "gated": false, + "cas": "expected_version", + "event": { + "type": "outreach.next_touch_decided", + "decision_id": "decision_8920a12b7a72", + "eligible": true, + "touch_index": 3, + "prior_touch_index": 2, + "dispatch_idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch" + }, + "operation_result": { + "operation": "append_event", + "status": "planned", + "before_version": 7, + "after_version": 8 + } + }, + "next_touch_packet": { + "schema": "runx.outreach.next_touch.v1", + "send_class": "outreach", + "principal": "sales-dev", + "channel": "email", + "audience": "jane@example.invalid", + "audience_role": "buyer", + "touch_index": 3, + "content_digest": "sha256:touch3", + "dispatch": { + "named_run": "send-as", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "consequence": "separate_governed_run_required", + "this_skill_sends": false + } + }, + "escalation": { + "required": false, + "lane": null + }, + "observations": [ + { + "type": "engagement_projection_read", + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-happy" + }, + "aggregate_id": "seq-acme-001:contact-jane", + "store_id": "runx.outreach.sequence_state.v1", + "version": 7 + }, + { + "type": "engagement_events_examined", + "events": [ + { + "type": "touch_sent", + "touch_index": 1, + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch1" + } + }, + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + } + ] + }, + { + "type": "sequence_position", + "current_touch_index": 2 + }, + { + "type": "eligibility_verdict", + "eligible": true, + "reason": "next_touch_due" + }, + { + "type": "append_event", + "operation_result": { + "operation": "append_event", + "status": "planned", + "before_version": 7, + "after_version": 8 + }, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "before_version": 7, + "after_version": 8 + }, + { + "type": "next_touch", + "index": 3, + "channel": "email", + "send_class": "outreach", + "content_digest": "sha256:touch3" + } + ] +} diff --git a/skills/outreach-sequencer/evidence/direct-runner-missing-state.json b/skills/outreach-sequencer/evidence/direct-runner-missing-state.json new file mode 100644 index 000000000..095f18a0d --- /dev/null +++ b/skills/outreach-sequencer/evidence/direct-runner-missing-state.json @@ -0,0 +1,19 @@ +{ + "schema": "runx.outreach.sequencer.v1", + "status": "needs_agent", + "decision": { + "eligible": false, + "reason": "unreadable_engagement_state" + }, + "escalation": { + "required": true, + "lane": "human_approval", + "reason_code": "unreadable_engagement_state", + "message": "engagement_projection.operation_result must be an ok read_projection." + }, + "stop_state": { + "state": "needs_human", + "reason_code": "unreadable_engagement_state", + "message": "engagement_projection.operation_result must be an ok read_projection." + } +} diff --git a/skills/outreach-sequencer/evidence/direct-runner-stop.json b/skills/outreach-sequencer/evidence/direct-runner-stop.json new file mode 100644 index 000000000..f9d990c53 --- /dev/null +++ b/skills/outreach-sequencer/evidence/direct-runner-stop.json @@ -0,0 +1,100 @@ +{ + "schema": "runx.outreach.sequencer.v1", + "status": "sealed", + "decision": { + "eligible": false, + "reason": "replied" + }, + "engagement_projection": { + "operation": "read_projection", + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1", + "aggregate_id": "seq-acme-002:contact-lee", + "version": 4, + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-stop" + } + }, + "engagement_events": [ + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-24T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + }, + { + "type": "replied", + "occurred_at": "2026-06-27T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + } + ], + "stop_state": { + "state": "stopped", + "reason": "engagement stream contains replied", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + }, + "observations": [ + { + "type": "engagement_projection_read", + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-stop" + }, + "aggregate_id": "seq-acme-002:contact-lee", + "store_id": "runx.outreach.sequence_state.v1", + "version": 4 + }, + { + "type": "engagement_events_examined", + "events": [ + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-24T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + }, + { + "type": "replied", + "occurred_at": "2026-06-27T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + } + ] + }, + { + "type": "sequence_position", + "current_touch_index": 2 + }, + { + "type": "refused_reason", + "reason": "replied", + "linked_operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + } + ] +} diff --git a/skills/outreach-sequencer/evidence/dogfood-output-windows.json b/skills/outreach-sequencer/evidence/dogfood-output-windows.json new file mode 100644 index 000000000..8f1f4989c --- /dev/null +++ b/skills/outreach-sequencer/evidence/dogfood-output-windows.json @@ -0,0 +1,18 @@ +{ + "error": { + "code": "skill_error", + "message": "receipt store is unreadable: 参数错误。 (os error 87)" + }, + "registry_provenance": { + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "registry_key_id": "runx-registry-ed25519-v1", + "registry_source": "remote https://api.runx.ai", + "registry_source_fingerprint": "ba1ac16b631195fd", + "skill_id": "vidshidden/outreach-sequencer", + "trust_state": "trusted", + "trust_tier": "community", + "version": "sha-8248a4585211" + }, + "status": "failure" +} diff --git a/skills/outreach-sequencer/evidence/evidence.json b/skills/outreach-sequencer/evidence/evidence.json new file mode 100644 index 000000000..f318f55f2 --- /dev/null +++ b/skills/outreach-sequencer/evidence/evidence.json @@ -0,0 +1,245 @@ +{ + "schema": "frantic.evidence.v1", + "bounty": "#74", + "package_name": "outreach-sequencer", + "runx_cli_version": "runx-cli 0.6.14", + "summary": "Published outreach-sequencer runx skill. The skill reads a pinned data-store engagement projection for one sequence/contact aggregate, decides whether the next touch is eligible, appends an ungated decision event, and emits a handoff-only runx.outreach.next_touch.v1 packet for a separate governed send-as run.", + "published": { + "skill_id": "vidshidden/outreach-sequencer", + "version": "sha-8248a4585211", + "registry_ref": "vidshidden/outreach-sequencer@sha-8248a4585211", + "public_url": "https://runx.ai/x/vidshidden/outreach-sequencer@sha-8248a4585211", + "install_command": "runx add vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai", + "run_command": "runx skill vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "trust_tier": "community", + "maturity": "beta" + }, + "hosted_harness": { + "status": "passed", + "case_count": 3, + "assertion_error_count": 0, + "assertion_errors": [], + "case_names": [ + "happy_next_touch", + "stop_replied", + "missing_state_needs_agent" + ], + "receipt_ids": [ + "sha256:0cc476175ec20f917b0f751f4a7215041607100c02d37cf245fe5acf00b7e27f", + "sha256:8e0e53664b9b2e6ccbc4974083babba979751827cb983308176efe50bc0dbb36", + "sha256:55945b1c9000f5267ed98f939de071d4676d6b0f34f571625dc28095a69defad" + ], + "graph_case_count": 0, + "evidence_id": "runx-harness:vidshidden/outreach-sequencer:sha-8248a4585211", + "evidence_url": "https://runx.ai/x/vidshidden/outreach-sequencer#harness" + }, + "clean_install": { + "status": "success", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b" + }, + "dogfood": { + "package": "vidshidden/outreach-sequencer@sha-8248a4585211", + "input": { + "fixture": "skills/outreach-sequencer/fixtures/happy-next-touch.json", + "aggregate_id": "seq-acme-001:contact-jane", + "store_id": "runx.outreach.sequence_state.v1", + "current_touch_index": 2, + "next_touch_index": 3, + "engagement_projection_operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-happy" + } + }, + "command": "runx skill vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai --json -R skills/outreach-sequencer/evidence/dogfood-receipts", + "status": "pending_ubuntu", + "receipt_ref": "pending-ubuntu-dogfood", + "verify_verdict": { + "valid": false, + "signature_mode": null, + "root_receipt_id": null, + "findings": [ + "Ubuntu dogfood verify has not been recorded yet." + ] + }, + "harness_cases": [ + { + "name": "happy_next_touch", + "status": "sealed" + }, + { + "name": "stop_replied", + "status": "sealed" + }, + { + "name": "missing_state_needs_agent", + "status": "refused" + } + ], + "windows_attempt_status": "failure", + "windows_attempt_note": "Windows resolved trusted registry provenance, then failed writing the local receipt store with os error 87. The outreach-sequencer dogfood workflow reruns the same dogfood on Ubuntu and commits durable dogfood-output.json, dogfood-verify.json, and dogfood-receipt.json.", + "registry_provenance": { + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "registry_key_id": "runx-registry-ed25519-v1", + "registry_source": "remote https://api.runx.ai", + "registry_source_fingerprint": "ba1ac16b631195fd", + "skill_id": "vidshidden/outreach-sequencer", + "trust_state": "trusted", + "trust_tier": "community", + "version": "sha-8248a4585211" + } + }, + "observations": [ + { + "type": "runx_cli_version", + "value": "runx-cli 0.6.14" + }, + { + "type": "hosted_harness", + "status": "passed", + "case_names": [ + "happy_next_touch", + "stop_replied", + "missing_state_needs_agent" + ], + "receipt_refs": [ + "runx:receipt:sha256:0cc476175ec20f917b0f751f4a7215041607100c02d37cf245fe5acf00b7e27f", + "runx:receipt:sha256:8e0e53664b9b2e6ccbc4974083babba979751827cb983308176efe50bc0dbb36", + "runx:receipt:sha256:55945b1c9000f5267ed98f939de071d4676d6b0f34f571625dc28095a69defad" + ] + }, + { + "type": "eligibility_verdict", + "decision": { + "eligible": true, + "reason": "next_touch_due" + }, + "reason": "next_touch_due", + "packet_schema": "runx.outreach.next_touch.v1", + "send_class": "outreach" + }, + { + "type": "engagement_events_examined", + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-happy" + }, + "events": [ + { + "type": "touch_sent", + "touch_index": 1, + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch1" + } + }, + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + } + ] + }, + { + "type": "append_event", + "operation_result": { + "operation": "append_event", + "status": "planned", + "before_version": 7, + "after_version": 8 + }, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "before_version": 7, + "after_version": 8 + }, + { + "type": "next_touch", + "index": 3, + "channel": "email", + "audience": "jane@example.invalid", + "content_digest": "sha256:touch3", + "dispatch": { + "named_run": "send-as", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "consequence": "separate_governed_run_required", + "this_skill_sends": false + } + }, + { + "type": "stop_replied", + "decision": { + "eligible": false, + "reason": "replied" + }, + "stop_state": { + "state": "stopped", + "reason": "engagement stream contains replied", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + }, + "has_packet": false, + "refused_reason": { + "type": "refused_reason", + "reason": "replied", + "linked_operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + } + }, + { + "type": "missing_state_escalation", + "status": "needs_agent", + "escalation": { + "required": true, + "lane": "human_approval", + "reason_code": "unreadable_engagement_state", + "message": "engagement_projection.operation_result must be an ok read_projection." + }, + "stop_state": { + "state": "needs_human", + "reason_code": "unreadable_engagement_state", + "message": "engagement_projection.operation_result must be an ok read_projection." + } + }, + { + "type": "dogfood_verify", + "receipt_ref": "pending-ubuntu-dogfood", + "verify_verdict": { + "valid": false, + "signature_mode": null, + "root_receipt_id": null, + "findings": [ + "Ubuntu dogfood verify has not been recorded yet." + ] + } + }, + { + "type": "artifact_urls", + "public_url": "https://runx.ai/x/vidshidden/outreach-sequencer@sha-8248a4585211", + "pr_url": "https://github.com/runxhq/runx/pull/PLACEHOLDER", + "source_url": "https://github.com/VidsHidden/runx/tree/outreach-sequencer/skills/outreach-sequencer", + "x_yaml": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/X.yaml", + "skill_md": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/SKILL.md", + "evidence_json": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/evidence/evidence.json", + "verification_json": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/evidence/verification.json", + "report": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/evidence/report.md" + } + ] +} diff --git a/skills/outreach-sequencer/evidence/local-harness-windows-c-tmp.json b/skills/outreach-sequencer/evidence/local-harness-windows-c-tmp.json new file mode 100644 index 000000000..c468f79db --- /dev/null +++ b/skills/outreach-sequencer/evidence/local-harness-windows-c-tmp.json @@ -0,0 +1,15 @@ +{ + "status": "failed", + "case_count": 2, + "assertion_error_count": 2, + "assertion_errors": [ + "happy_next_touch: receipt store is unreadable: 拒绝访问。 (os error 5)", + "stop_replied: receipt store is unreadable: 拒绝访问。 (os error 5)" + ], + "case_names": [ + "happy_next_touch", + "stop_replied" + ], + "receipt_ids": [], + "graph_case_count": 0 +} diff --git a/skills/outreach-sequencer/evidence/local-harness-windows-workspace.json b/skills/outreach-sequencer/evidence/local-harness-windows-workspace.json new file mode 100644 index 000000000..59b25e608 --- /dev/null +++ b/skills/outreach-sequencer/evidence/local-harness-windows-workspace.json @@ -0,0 +1,15 @@ +{ + "status": "failed", + "case_count": 2, + "assertion_error_count": 2, + "assertion_errors": [ + "happy_next_touch: receipt store is unreadable: 参数错误。 (os error 87)", + "stop_replied: receipt store is unreadable: 参数错误。 (os error 87)" + ], + "case_names": [ + "happy_next_touch", + "stop_replied" + ], + "receipt_ids": [], + "graph_case_count": 0 +} diff --git a/skills/outreach-sequencer/evidence/local-harness-windows.json b/skills/outreach-sequencer/evidence/local-harness-windows.json new file mode 100644 index 000000000..59b25e608 --- /dev/null +++ b/skills/outreach-sequencer/evidence/local-harness-windows.json @@ -0,0 +1,15 @@ +{ + "status": "failed", + "case_count": 2, + "assertion_error_count": 2, + "assertion_errors": [ + "happy_next_touch: receipt store is unreadable: 参数错误。 (os error 87)", + "stop_replied: receipt store is unreadable: 参数错误。 (os error 87)" + ], + "case_names": [ + "happy_next_touch", + "stop_replied" + ], + "receipt_ids": [], + "graph_case_count": 0 +} diff --git a/skills/outreach-sequencer/evidence/publish-response.json b/skills/outreach-sequencer/evidence/publish-response.json new file mode 100644 index 000000000..e313cd415 --- /dev/null +++ b/skills/outreach-sequencer/evidence/publish-response.json @@ -0,0 +1 @@ +{"status":"success","publish":{"status":"published","skill_id":"vidshidden/outreach-sequencer","owner":"vidshidden","name":"outreach-sequencer","version":"sha-8248a4585211","digest":"53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e","profile_digest":"f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b","trust_tier":"community","maturity":"beta","install_command":"runx add vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai","run_command":"runx skill vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai","public_url":"https://runx.ai/x/vidshidden/outreach-sequencer","harness":{"status":"passed","case_count":3,"assertion_error_count":0,"assertion_errors":[],"case_names":["happy_next_touch","stop_replied","missing_state_needs_agent"],"receipt_ids":["sha256:0cc476175ec20f917b0f751f4a7215041607100c02d37cf245fe5acf00b7e27f","sha256:8e0e53664b9b2e6ccbc4974083babba979751827cb983308176efe50bc0dbb36","sha256:55945b1c9000f5267ed98f939de071d4676d6b0f34f571625dc28095a69defad"],"graph_case_count":0,"evidence_id":"runx-harness:vidshidden/outreach-sequencer:sha-8248a4585211","evidence_url":"https://runx.ai/x/vidshidden/outreach-sequencer#harness"}}} \ No newline at end of file diff --git a/skills/outreach-sequencer/evidence/report.md b/skills/outreach-sequencer/evidence/report.md new file mode 100644 index 000000000..2e66e0fd6 --- /dev/null +++ b/skills/outreach-sequencer/evidence/report.md @@ -0,0 +1,39 @@ +# outreach-sequencer delivery report + +## Package + +- Package: vidshidden/outreach-sequencer@sha-8248a4585211 +- Public URL: https://runx.ai/x/vidshidden/outreach-sequencer@sha-8248a4585211 +- PR URL: https://github.com/runxhq/runx/pull/PLACEHOLDER +- Source URL: https://github.com/VidsHidden/runx/tree/outreach-sequencer/skills/outreach-sequencer +- Raw X.yaml: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/X.yaml +- Raw SKILL.md: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/SKILL.md +- Evidence JSON: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/evidence/evidence.json +- Verification JSON: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/evidence/verification.json +- Report: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/evidence/report.md + +## Verification + +- runx CLI version: runx-cli 0.6.14. +- Publish method: direct equivalent of `runx registry publish ./skills/outreach-sequencer/SKILL.md --registry https://api.runx.ai` using the same remote /v1/skills API because Windows local publish harness hits receipt-store os error 87. +- Hosted harness status: passed, cases happy_next_touch, stop_replied, missing_state_needs_agent. +- Clean install command: `runx add vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai`. +- Dogfood command: `runx skill vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai --json -R skills/outreach-sequencer/evidence/dogfood-receipts`. +- Dogfood receipt: pending-ubuntu-dogfood. +- runx verify verdict: pending or not valid yet; signature mode pending. +- Windows local dogfood status: failure; expected receipt-store issue is recorded in dogfood-output-windows.json. + +## Behavior + +- `happy_next_touch` reads data-store projection version 7, sees no reply or unsubscribe, and emits one `runx.outreach.next_touch.v1` handoff packet for touch 3. +- The packet is handoff-only: it names `send-as`, keeps `this_skill_sends: false`, and requires a separate governed downstream run. +- The append event is ungated, uses idempotency key `outreach-sequencer:seq-acme-001:contact-jane:touch3`, and moves version 7 to 8. +- `stop_replied` reads a linked reply event and seals with no next-touch packet. +- `missing_state_needs_agent` returns `needs_agent` for unreadable engagement state instead of guessing. + +## New User + +- Install: `runx add vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai`. +- Run with bounded JSON inputs matching `fixtures/happy-next-touch.json`. +- Verify receipts with `runx verify --receipt-dir skills/outreach-sequencer/evidence/dogfood-receipts --json`. +- Trust the skill only as a decision and handoff packet generator; it never sends outreach or mints dispatch authority. diff --git a/skills/outreach-sequencer/evidence/verification.json b/skills/outreach-sequencer/evidence/verification.json new file mode 100644 index 000000000..9216c7094 --- /dev/null +++ b/skills/outreach-sequencer/evidence/verification.json @@ -0,0 +1,47 @@ +{ + "schema": "frantic.verification.v1", + "bounty": "#74", + "package": "vidshidden/outreach-sequencer@sha-8248a4585211", + "runx_cli": "0.6.14", + "public_url": "https://runx.ai/x/vidshidden/outreach-sequencer@sha-8248a4585211", + "hosted_harness": { + "status": "passed", + "case_count": 3, + "case_names": [ + "happy_next_touch", + "stop_replied", + "missing_state_needs_agent" + ], + "receipt_refs": [ + "runx:receipt:sha256:0cc476175ec20f917b0f751f4a7215041607100c02d37cf245fe5acf00b7e27f", + "runx:receipt:sha256:8e0e53664b9b2e6ccbc4974083babba979751827cb983308176efe50bc0dbb36", + "runx:receipt:sha256:55945b1c9000f5267ed98f939de071d4676d6b0f34f571625dc28095a69defad" + ], + "evidence_id": "runx-harness:vidshidden/outreach-sequencer:sha-8248a4585211", + "evidence_url": "https://runx.ai/x/vidshidden/outreach-sequencer#harness" + }, + "clean_install": { + "command": "runx add vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai", + "status": "success", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e" + }, + "local_runner": { + "happy_next_touch": "passed", + "stop_replied": "passed", + "missing_state_needs_agent": "passed" + }, + "dogfood": { + "status": "pending_ubuntu", + "receipt_ref": "pending-ubuntu-dogfood", + "verify_valid": false, + "windows_status": "failure", + "windows_error": "receipt store is unreadable: 参数错误。 (os error 87)", + "ubuntu_actions_workflow": ".github/workflows/outreach-sequencer-dogfood.yml", + "expected_outputs": [ + "skills/outreach-sequencer/evidence/dogfood-output.json", + "skills/outreach-sequencer/evidence/dogfood-verify.json", + "skills/outreach-sequencer/evidence/dogfood-receipt.json" + ] + }, + "receipt_ref": "pending-ubuntu-dogfood" +} diff --git a/skills/outreach-sequencer/fixtures/happy-next-touch.json b/skills/outreach-sequencer/fixtures/happy-next-touch.json new file mode 100644 index 000000000..f00d1b6de --- /dev/null +++ b/skills/outreach-sequencer/fixtures/happy-next-touch.json @@ -0,0 +1,93 @@ +{ + "sequence_definition": { + "touches": [ + { + "index": 1, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Quick fit check", + "content_digest": "sha256:touch1" + }, + { + "index": 2, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Proof point", + "content_digest": "sha256:touch2" + }, + { + "index": 3, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Relevant workflow", + "content_digest": "sha256:touch3" + }, + { + "index": 4, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Last useful note", + "content_digest": "sha256:touch4" + }, + { + "index": 5, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Close the loop", + "content_digest": "sha256:touch5" + } + ], + "rules": { + "min_days_apart": 3, + "stop_on": ["replied", "unsubscribed"] + } + }, + "aggregate_id": "seq-acme-001:contact-jane", + "contact_ref": { + "principal": "sales-dev", + "audience": "jane@example.invalid", + "channel": "email" + }, + "current_touch_index": 2, + "store_id": "runx.outreach.sequence_state.v1", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "expected_version": 7, + "engagement_projection": { + "aggregate_id": "seq-acme-001:contact-jane", + "store_id": "runx.outreach.sequence_state.v1", + "version": 7, + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-happy" + }, + "events": [ + { + "type": "touch_sent", + "touch_index": 1, + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch1" + } + }, + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + } + ], + "now": "2026-06-30T12:00:00Z" + } +} diff --git a/skills/outreach-sequencer/fixtures/missing-state.json b/skills/outreach-sequencer/fixtures/missing-state.json new file mode 100644 index 000000000..2c0df4a3b --- /dev/null +++ b/skills/outreach-sequencer/fixtures/missing-state.json @@ -0,0 +1,40 @@ +{ + "sequence_definition": { + "touches": [ + { + "index": 1, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Quick fit check", + "content_digest": "sha256:touch1" + } + ], + "rules": { + "min_days_apart": 3, + "stop_on": ["replied", "unsubscribed"] + } + }, + "aggregate_id": "seq-acme-003:contact-missing", + "contact_ref": { + "principal": "sales-dev", + "audience": "missing@example.invalid", + "channel": "email" + }, + "current_touch_index": 0, + "store_id": "runx.outreach.sequence_state.v1", + "idempotency_key": "outreach-sequencer:seq-acme-003:contact-missing:needs-agent", + "expected_version": 0, + "engagement_projection": { + "aggregate_id": "seq-acme-003:contact-missing", + "store_id": "runx.outreach.sequence_state.v1", + "version": 0, + "operation_result": { + "operation": "read_projection", + "status": "missing", + "receipt_ref": "runx:receipt:fixture-read-missing" + }, + "events": [], + "now": "2026-06-30T12:00:00Z" + } +} diff --git a/skills/outreach-sequencer/fixtures/stop-replied.json b/skills/outreach-sequencer/fixtures/stop-replied.json new file mode 100644 index 000000000..55c49c5d4 --- /dev/null +++ b/skills/outreach-sequencer/fixtures/stop-replied.json @@ -0,0 +1,76 @@ +{ + "sequence_definition": { + "touches": [ + { + "index": 1, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Quick fit check", + "content_digest": "sha256:touch1" + }, + { + "index": 2, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Proof point", + "content_digest": "sha256:touch2" + }, + { + "index": 3, + "channel": "email", + "principal": "sales-dev", + "audience": "buyer", + "subject": "Relevant workflow", + "content_digest": "sha256:touch3" + } + ], + "rules": { + "min_days_apart": 3, + "stop_on": ["replied", "unsubscribed"] + } + }, + "aggregate_id": "seq-acme-002:contact-lee", + "contact_ref": { + "principal": "sales-dev", + "audience": "lee@example.invalid", + "channel": "email" + }, + "current_touch_index": 2, + "store_id": "runx.outreach.sequence_state.v1", + "idempotency_key": "outreach-sequencer:seq-acme-002:contact-lee:reply-stop", + "expected_version": 4, + "engagement_projection": { + "aggregate_id": "seq-acme-002:contact-lee", + "store_id": "runx.outreach.sequence_state.v1", + "version": 4, + "operation_result": { + "operation": "read_projection", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-read-stop" + }, + "events": [ + { + "type": "touch_sent", + "touch_index": 2, + "occurred_at": "2026-06-24T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-touch2" + } + }, + { + "type": "replied", + "occurred_at": "2026-06-27T12:00:00Z", + "operation_result": { + "operation": "append_event", + "status": "ok", + "receipt_ref": "runx:receipt:fixture-reply" + } + } + ], + "now": "2026-06-30T12:00:00Z" + } +} diff --git a/skills/outreach-sequencer/run.mjs b/skills/outreach-sequencer/run.mjs new file mode 100644 index 000000000..757c2c8a7 --- /dev/null +++ b/skills/outreach-sequencer/run.mjs @@ -0,0 +1,385 @@ +import fs from "node:fs"; +import { createHash } from "node:crypto"; + +const inputs = readInputs(); + +try { + const output = judgeOutreach(inputs); + emit(output); +} catch (error) { + emit(needsHuman(error.code ?? "invalid_input", error.message)); + process.exit(2); +} + +function judgeOutreach(rawInputs) { + const sequence = objectValue(rawInputs.sequence_definition); + const aggregateId = stringInput(rawInputs.aggregate_id, "aggregate_id"); + const contactRef = objectValue(rawInputs.contact_ref); + const storeId = stringInput(rawInputs.store_id, "store_id"); + const idempotencyKey = stringInput(rawInputs.idempotency_key, "idempotency_key"); + const expectedVersion = numberInput(rawInputs.expected_version, "expected_version"); + const engagement = normalizeEngagement(rawInputs.engagement_projection, aggregateId, storeId, expectedVersion); + const currentTouchIndex = normalizeCurrentTouch(rawInputs.current_touch_index, engagement.events); + + validateSequence(sequence); + validateContact(contactRef); + + const projectionRead = { + operation: "read_projection", + store: "registry:runx/data-store@0.1.2", + store_id: storeId, + aggregate_id: aggregateId, + version: engagement.version, + operation_result: engagement.operation_result, + }; + + const stopEvent = engagement.events.find((event) => ["replied", "reply", "unsubscribed", "unsubscribe"].includes(event.type)); + if (stopEvent) { + return sealed({ + decision: { + eligible: false, + reason: stopEvent.type.includes("unsubscribe") ? "unsubscribed" : "replied", + }, + engagement_projection: projectionRead, + engagement_events: observedEvents(engagement.events), + stop_state: { + state: "stopped", + reason: `engagement stream contains ${stopEvent.type}`, + operation_result: stopEvent.operation_result, + }, + observations: baseObservations({ projectionRead, engagement, currentTouchIndex }).concat([ + { + type: "refused_reason", + reason: stopEvent.type.includes("unsubscribe") ? "unsubscribed" : "replied", + linked_operation_result: stopEvent.operation_result, + }, + ]), + }); + } + + const touches = sequence.touches.slice().sort((a, b) => a.index - b.index); + const nextTouch = touches.find((touch) => touch.index === currentTouchIndex + 1); + if (!nextTouch) { + return sealed({ + decision: { + eligible: false, + reason: "sequence_complete", + }, + engagement_projection: projectionRead, + engagement_events: observedEvents(engagement.events), + stop_state: { + state: "no_change", + reason: "no next touch remains in sequence_definition.touches", + }, + observations: baseObservations({ projectionRead, engagement, currentTouchIndex }), + }); + } + + const minDays = minDaysApart(sequence); + const priorTouch = latestSentTouch(engagement.events); + if (priorTouch) { + const now = parseDate(engagement.now, "engagement_projection.now"); + const priorAt = parseDate(priorTouch.occurred_at, "touch_sent.occurred_at"); + const ageDays = (now.getTime() - priorAt.getTime()) / 86400000; + if (ageDays < minDays) { + return sealed({ + decision: { + eligible: false, + reason: "min_days_apart_not_met", + }, + engagement_projection: projectionRead, + engagement_events: observedEvents(engagement.events), + stop_state: { + state: "wait", + reason: `prior touch was sent ${round(ageDays)} days ago; min_days_apart is ${minDays}`, + operation_result: priorTouch.operation_result, + }, + observations: baseObservations({ projectionRead, engagement, currentTouchIndex }).concat([ + { + type: "refused_reason", + reason: "min_days_apart_not_met", + prior_touch_index: priorTouch.touch_index, + prior_touch_at: priorTouch.occurred_at, + min_days_apart: minDays, + elapsed_days: round(ageDays), + linked_operation_result: priorTouch.operation_result, + }, + ]), + }); + } + } + + const afterVersion = expectedVersion + 1; + const dispatchIdempotencyKey = `${idempotencyKey}:dispatch`; + const appendEvent = { + operation: "append_event", + store: "registry:runx/data-store@0.1.2", + store_id: storeId, + aggregate_id: aggregateId, + idempotency_key: idempotencyKey, + expected_version: expectedVersion, + before_version: expectedVersion, + after_version: afterVersion, + gated: false, + cas: "expected_version", + event: { + type: "outreach.next_touch_decided", + decision_id: `decision_${hashShort(`${aggregateId}:${idempotencyKey}`)}`, + eligible: true, + touch_index: nextTouch.index, + prior_touch_index: currentTouchIndex, + dispatch_idempotency_key: dispatchIdempotencyKey, + }, + operation_result: { + operation: "append_event", + status: "planned", + before_version: expectedVersion, + after_version: afterVersion, + }, + }; + + const packet = { + schema: "runx.outreach.next_touch.v1", + send_class: nextTouch.send_class || "outreach", + principal: nextTouch.principal || contactRef.principal, + channel: nextTouch.channel || contactRef.channel, + audience: contactRef.audience, + audience_role: nextTouch.audience, + touch_index: nextTouch.index, + content_digest: nextTouch.content_digest, + dispatch: { + named_run: "send-as", + idempotency_key: dispatchIdempotencyKey, + consequence: "separate_governed_run_required", + this_skill_sends: false, + }, + }; + + return sealed({ + decision: { + eligible: true, + reason: "next_touch_due", + }, + engagement_projection: projectionRead, + engagement_events: observedEvents(engagement.events), + append_event: appendEvent, + next_touch_packet: packet, + escalation: { + required: false, + lane: null, + }, + observations: baseObservations({ projectionRead, engagement, currentTouchIndex }).concat([ + { + type: "eligibility_verdict", + eligible: true, + reason: "next_touch_due", + }, + { + type: "append_event", + operation_result: appendEvent.operation_result, + idempotency_key: appendEvent.idempotency_key, + before_version: appendEvent.before_version, + after_version: appendEvent.after_version, + }, + { + type: "next_touch", + index: nextTouch.index, + channel: packet.channel, + send_class: packet.send_class, + content_digest: nextTouch.content_digest, + }, + ]), + }); +} + +function sealed(fields) { + return { + schema: "runx.outreach.sequencer.v1", + status: "sealed", + ...fields, + }; +} + +function needsHuman(reasonCode, message) { + return { + schema: "runx.outreach.sequencer.v1", + status: "needs_agent", + decision: { + eligible: false, + reason: reasonCode, + }, + escalation: { + required: true, + lane: "human_approval", + reason_code: reasonCode, + message, + }, + stop_state: { + state: "needs_human", + reason_code: reasonCode, + message, + }, + }; +} + +function baseObservations({ projectionRead, engagement, currentTouchIndex }) { + return [ + { + type: "engagement_projection_read", + operation_result: projectionRead.operation_result, + aggregate_id: projectionRead.aggregate_id, + store_id: projectionRead.store_id, + version: projectionRead.version, + }, + { + type: "engagement_events_examined", + events: observedEvents(engagement.events), + }, + { + type: "sequence_position", + current_touch_index: currentTouchIndex, + }, + ]; +} + +function observedEvents(events) { + return events.map((event) => ({ + type: event.type, + touch_index: event.touch_index, + occurred_at: event.occurred_at, + operation_result: event.operation_result, + })); +} + +function validateSequence(sequence) { + if (!Array.isArray(sequence.touches) || sequence.touches.length === 0) { + throw problem("missing_sequence_definition", "sequence_definition.touches must contain at least one touch."); + } + for (const touch of sequence.touches) { + if (typeof touch.index !== "number") throw problem("invalid_touch_index", "every touch requires a numeric index."); + if (!touch.content_digest) throw problem("missing_content_digest", "every touch requires content_digest."); + } +} + +function validateContact(contactRef) { + if (!contactRef.principal) throw problem("missing_principal", "contact_ref.principal is required."); + if (!contactRef.audience) throw problem("missing_audience", "contact_ref.audience is required."); +} + +function normalizeEngagement(raw, aggregateId, storeId, expectedVersion) { + const projection = objectValue(raw); + if (projection.aggregate_id !== aggregateId) { + throw problem("projection_aggregate_mismatch", "engagement_projection.aggregate_id must match aggregate_id."); + } + if (projection.store_id !== storeId) { + throw problem("projection_store_mismatch", "engagement_projection.store_id must match store_id."); + } + if (projection.version !== expectedVersion) { + throw problem("expected_version_mismatch", "expected_version must match engagement_projection.version."); + } + const result = objectValue(projection.operation_result); + if (result.operation !== "read_projection" || result.status !== "ok") { + throw problem("unreadable_engagement_state", "engagement_projection.operation_result must be an ok read_projection."); + } + const events = Array.isArray(projection.events) ? projection.events : []; + for (const event of events) { + const op = objectValue(event.operation_result); + if (op.operation !== "append_event" || op.status !== "ok") { + throw problem("unlinked_engagement_event", "every engagement event must include an ok append_event operation_result."); + } + } + return { + version: projection.version, + operation_result: result, + events, + now: projection.now || new Date().toISOString(), + }; +} + +function normalizeCurrentTouch(value, events) { + if (typeof value === "number" && Number.isFinite(value)) return value; + return events + .filter((event) => event.type === "touch_sent" && typeof event.touch_index === "number") + .reduce((max, event) => Math.max(max, event.touch_index), 0); +} + +function minDaysApart(sequence) { + const value = objectValue(sequence.rules).min_days_apart; + return typeof value === "number" && Number.isFinite(value) ? value : 0; +} + +function latestSentTouch(events) { + return events + .filter((event) => event.type === "touch_sent" && event.occurred_at) + .sort((a, b) => String(b.occurred_at).localeCompare(String(a.occurred_at)))[0]; +} + +function parseDate(value, name) { + const date = new Date(value); + if (Number.isNaN(date.getTime())) throw problem("invalid_date", `${name} must be an ISO timestamp.`); + return date; +} + +function readInputs() { + if (process.env.RUNX_INPUTS_PATH) { + return JSON.parse(fs.readFileSync(process.env.RUNX_INPUTS_PATH, "utf8")); + } + if (process.env.RUNX_INPUTS_JSON) { + return JSON.parse(process.env.RUNX_INPUTS_JSON); + } + return { + sequence_definition: parseMaybeJson(process.env.RUNX_INPUT_SEQUENCE_DEFINITION), + aggregate_id: parseMaybeJson(process.env.RUNX_INPUT_AGGREGATE_ID), + contact_ref: parseMaybeJson(process.env.RUNX_INPUT_CONTACT_REF), + current_touch_index: parseMaybeJson(process.env.RUNX_INPUT_CURRENT_TOUCH_INDEX), + store_id: parseMaybeJson(process.env.RUNX_INPUT_STORE_ID), + idempotency_key: parseMaybeJson(process.env.RUNX_INPUT_IDEMPOTENCY_KEY), + expected_version: parseMaybeJson(process.env.RUNX_INPUT_EXPECTED_VERSION), + engagement_projection: parseMaybeJson(process.env.RUNX_INPUT_ENGAGEMENT_PROJECTION), + }; +} + +function parseMaybeJson(raw) { + if (raw === undefined || raw === "") return undefined; + try { + return JSON.parse(raw); + } catch { + return raw; + } +} + +function objectValue(value) { + return value && typeof value === "object" && !Array.isArray(value) ? value : {}; +} + +function stringInput(value, name) { + if (typeof value !== "string" || !value.trim()) { + throw problem(`missing_${name}`, `${name} is required.`); + } + return value.trim(); +} + +function numberInput(value, name) { + if (typeof value !== "number" || !Number.isFinite(value)) { + throw problem(`missing_${name}`, `${name} must be a finite number.`); + } + return value; +} + +function hashShort(value) { + return createHash("sha256").update(value).digest("hex").slice(0, 12); +} + +function round(value) { + return Math.round(value * 100) / 100; +} + +function problem(code, message) { + const error = new Error(message); + error.code = code; + return error; +} + +function emit(value) { + process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); +} From 63d360c704e4df51d82fb5c7001057f7e750d6ef Mon Sep 17 00:00:00 2001 From: VidsHidden Date: Tue, 30 Jun 2026 16:27:05 +0000 Subject: [PATCH 2/5] Add outreach-sequencer dogfood evidence [skip dogfood] --- .../evidence/clean-install.json | 43 ++ .../evidence/dogfood-output.json | 626 ++++++++++++++++++ .../evidence/dogfood-receipt.json | 1 + .../evidence/dogfood-verify.json | 15 + .../outreach-sequencer/evidence/evidence.json | 30 +- skills/outreach-sequencer/evidence/report.md | 4 +- .../evidence/verification.json | 8 +- 7 files changed, 705 insertions(+), 22 deletions(-) create mode 100644 skills/outreach-sequencer/evidence/clean-install.json create mode 100644 skills/outreach-sequencer/evidence/dogfood-output.json create mode 100644 skills/outreach-sequencer/evidence/dogfood-receipt.json create mode 100644 skills/outreach-sequencer/evidence/dogfood-verify.json diff --git a/skills/outreach-sequencer/evidence/clean-install.json b/skills/outreach-sequencer/evidence/clean-install.json new file mode 100644 index 000000000..82e3fdf08 --- /dev/null +++ b/skills/outreach-sequencer/evidence/clean-install.json @@ -0,0 +1,43 @@ +{ + "status": "success", + "registry": { + "action": "install", + "source": "remote", + "ref": "vidshidden/outreach-sequencer@sha-8248a4585211", + "install": { + "status": "installed", + "destination": "/home/runner/work/runx/runx/skills/outreach-sequencer/evidence/clean-install/vidshidden/outreach-sequencer/sha-8248a4585211/SKILL.md", + "skill_name": "outreach-sequencer", + "source": "runx-registry", + "source_label": "runx registry", + "skill_id": "vidshidden/outreach-sequencer", + "version": "sha-8248a4585211", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "profile_state_path": "/home/runner/work/runx/runx/skills/outreach-sequencer/evidence/clean-install/vidshidden/outreach-sequencer/sha-8248a4585211/.runx/profile.json", + "runner_names": [ + "judge" + ], + "trust_tier": "community" + }, + "receipt_metadata": { + "destination": "/home/runner/work/runx/runx/skills/outreach-sequencer/evidence/clean-install/vidshidden/outreach-sequencer/sha-8248a4585211/SKILL.md", + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "install_count": 2, + "package_digest": "2458ce837db4cfe997eddaca640a0ca9d2aa6d250385fd90e21a165c8272c4ba", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "publisher": { + "display_name": "薪宇", + "handle": "vidshidden", + "id": "user_9c5d6c8f9504a7bf31aacfd5", + "kind": "user" + }, + "ref": "vidshidden/outreach-sequencer@sha-8248a4585211", + "skill_id": "vidshidden/outreach-sequencer", + "source_label": "runx registry", + "status": "installed", + "trust_tier": "community", + "version": "sha-8248a4585211" + } + } +} diff --git a/skills/outreach-sequencer/evidence/dogfood-output.json b/skills/outreach-sequencer/evidence/dogfood-output.json new file mode 100644 index 000000000..eb8fc16b4 --- /dev/null +++ b/skills/outreach-sequencer/evidence/dogfood-output.json @@ -0,0 +1,626 @@ +{ + "closure": { + "closed_at": "2026-06-30T16:27:04.909Z", + "disposition": "closed", + "reason_code": "process_closed", + "summary": "cli-tool judge completed" + }, + "execution": { + "exit_code": 0, + "skill_claim": { + "append_event": { + "after_version": 8, + "aggregate_id": "seq-acme-001:contact-jane", + "before_version": 7, + "cas": "expected_version", + "event": { + "decision_id": "decision_8920a12b7a72", + "dispatch_idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "eligible": true, + "prior_touch_index": 2, + "touch_index": 3, + "type": "outreach.next_touch_decided" + }, + "expected_version": 7, + "gated": false, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "operation": "append_event", + "operation_result": { + "after_version": 8, + "before_version": 7, + "operation": "append_event", + "status": "planned" + }, + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1" + }, + "decision": { + "eligible": true, + "reason": "next_touch_due" + }, + "engagement_events": [ + { + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch1", + "status": "ok" + }, + "touch_index": 1, + "type": "touch_sent" + }, + { + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch2", + "status": "ok" + }, + "touch_index": 2, + "type": "touch_sent" + } + ], + "engagement_projection": { + "aggregate_id": "seq-acme-001:contact-jane", + "operation": "read_projection", + "operation_result": { + "operation": "read_projection", + "receipt_ref": "runx:receipt:fixture-read-happy", + "status": "ok" + }, + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1", + "version": 7 + }, + "escalation": { + "lane": null, + "required": false + }, + "next_touch_packet": { + "audience": "jane@example.invalid", + "audience_role": "buyer", + "channel": "email", + "content_digest": "sha256:touch3", + "dispatch": { + "consequence": "separate_governed_run_required", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "named_run": "send-as", + "this_skill_sends": false + }, + "principal": "sales-dev", + "schema": "runx.outreach.next_touch.v1", + "send_class": "outreach", + "touch_index": 3 + }, + "observations": [ + { + "aggregate_id": "seq-acme-001:contact-jane", + "operation_result": { + "operation": "read_projection", + "receipt_ref": "runx:receipt:fixture-read-happy", + "status": "ok" + }, + "store_id": "runx.outreach.sequence_state.v1", + "type": "engagement_projection_read", + "version": 7 + }, + { + "events": [ + { + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch1", + "status": "ok" + }, + "touch_index": 1, + "type": "touch_sent" + }, + { + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch2", + "status": "ok" + }, + "touch_index": 2, + "type": "touch_sent" + } + ], + "type": "engagement_events_examined" + }, + { + "current_touch_index": 2, + "type": "sequence_position" + }, + { + "eligible": true, + "reason": "next_touch_due", + "type": "eligibility_verdict" + }, + { + "after_version": 8, + "before_version": 7, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "operation_result": { + "after_version": 8, + "before_version": 7, + "operation": "append_event", + "status": "planned" + }, + "type": "append_event" + }, + { + "channel": "email", + "content_digest": "sha256:touch3", + "index": 3, + "send_class": "outreach", + "type": "next_touch" + } + ], + "schema": "runx.outreach.sequencer.v1", + "status": "sealed" + }, + "stderr": "", + "stdout": "{\n \"schema\": \"runx.outreach.sequencer.v1\",\n \"status\": \"sealed\",\n \"decision\": {\n \"eligible\": true,\n \"reason\": \"next_touch_due\"\n },\n \"engagement_projection\": {\n \"operation\": \"read_projection\",\n \"store\": \"registry:runx/data-store@0.1.2\",\n \"store_id\": \"runx.outreach.sequence_state.v1\",\n \"aggregate_id\": \"seq-acme-001:contact-jane\",\n \"version\": 7,\n \"operation_result\": {\n \"operation\": \"read_projection\",\n \"receipt_ref\": \"runx:receipt:fixture-read-happy\",\n \"status\": \"ok\"\n }\n },\n \"engagement_events\": [\n {\n \"type\": \"touch_sent\",\n \"touch_index\": 1,\n \"occurred_at\": \"2026-06-20T12:00:00Z\",\n \"operation_result\": {\n \"operation\": \"append_event\",\n \"receipt_ref\": \"runx:receipt:fixture-touch1\",\n \"status\": \"ok\"\n }\n },\n {\n \"type\": \"touch_sent\",\n \"touch_index\": 2,\n \"occurred_at\": \"2026-06-25T12:00:00Z\",\n \"operation_result\": {\n \"operation\": \"append_event\",\n \"receipt_ref\": \"runx:receipt:fixture-touch2\",\n \"status\": \"ok\"\n }\n }\n ],\n \"append_event\": {\n \"operation\": \"append_event\",\n \"store\": \"registry:runx/data-store@0.1.2\",\n \"store_id\": \"runx.outreach.sequence_state.v1\",\n \"aggregate_id\": \"seq-acme-001:contact-jane\",\n \"idempotency_key\": \"outreach-sequencer:seq-acme-001:contact-jane:touch3\",\n \"expected_version\": 7,\n \"before_version\": 7,\n \"after_version\": 8,\n \"gated\": false,\n \"cas\": \"expected_version\",\n \"event\": {\n \"type\": \"outreach.next_touch_decided\",\n \"decision_id\": \"decision_8920a12b7a72\",\n \"eligible\": true,\n \"touch_index\": 3,\n \"prior_touch_index\": 2,\n \"dispatch_idempotency_key\": \"outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch\"\n },\n \"operation_result\": {\n \"operation\": \"append_event\",\n \"status\": \"planned\",\n \"before_version\": 7,\n \"after_version\": 8\n }\n },\n \"next_touch_packet\": {\n \"schema\": \"runx.outreach.next_touch.v1\",\n \"send_class\": \"outreach\",\n \"principal\": \"sales-dev\",\n \"channel\": \"email\",\n \"audience\": \"jane@example.invalid\",\n \"audience_role\": \"buyer\",\n \"touch_index\": 3,\n \"content_digest\": \"sha256:touch3\",\n \"dispatch\": {\n \"named_run\": \"send-as\",\n \"idempotency_key\": \"outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch\",\n \"consequence\": \"separate_governed_run_required\",\n \"this_skill_sends\": false\n }\n },\n \"escalation\": {\n \"required\": false,\n \"lane\": null\n },\n \"observations\": [\n {\n \"type\": \"engagement_projection_read\",\n \"operation_result\": {\n \"operation\": \"read_projection\",\n \"receipt_ref\": \"runx:receipt:fixture-read-happy\",\n \"status\": \"ok\"\n },\n \"aggregate_id\": \"seq-acme-001:contact-jane\",\n \"store_id\": \"runx.outreach.sequence_state.v1\",\n \"version\": 7\n },\n {\n \"type\": \"engagement_events_examined\",\n \"events\": [\n {\n \"type\": \"touch_sent\",\n \"touch_index\": 1,\n \"occurred_at\": \"2026-06-20T12:00:00Z\",\n \"operation_result\": {\n \"operation\": \"append_event\",\n \"receipt_ref\": \"runx:receipt:fixture-touch1\",\n \"status\": \"ok\"\n }\n },\n {\n \"type\": \"touch_sent\",\n \"touch_index\": 2,\n \"occurred_at\": \"2026-06-25T12:00:00Z\",\n \"operation_result\": {\n \"operation\": \"append_event\",\n \"receipt_ref\": \"runx:receipt:fixture-touch2\",\n \"status\": \"ok\"\n }\n }\n ]\n },\n {\n \"type\": \"sequence_position\",\n \"current_touch_index\": 2\n },\n {\n \"type\": \"eligibility_verdict\",\n \"eligible\": true,\n \"reason\": \"next_touch_due\"\n },\n {\n \"type\": \"append_event\",\n \"operation_result\": {\n \"operation\": \"append_event\",\n \"status\": \"planned\",\n \"before_version\": 7,\n \"after_version\": 8\n },\n \"idempotency_key\": \"outreach-sequencer:seq-acme-001:contact-jane:touch3\",\n \"before_version\": 7,\n \"after_version\": 8\n },\n {\n \"type\": \"next_touch\",\n \"index\": 3,\n \"channel\": \"email\",\n \"send_class\": \"outreach\",\n \"content_digest\": \"sha256:touch3\"\n }\n ]\n}\n", + "structured_output": { + "append_event": { + "after_version": 8, + "aggregate_id": "seq-acme-001:contact-jane", + "before_version": 7, + "cas": "expected_version", + "event": { + "decision_id": "decision_8920a12b7a72", + "dispatch_idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "eligible": true, + "prior_touch_index": 2, + "touch_index": 3, + "type": "outreach.next_touch_decided" + }, + "expected_version": 7, + "gated": false, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "operation": "append_event", + "operation_result": { + "after_version": 8, + "before_version": 7, + "operation": "append_event", + "status": "planned" + }, + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1" + }, + "decision": { + "eligible": true, + "reason": "next_touch_due" + }, + "engagement_events": [ + { + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch1", + "status": "ok" + }, + "touch_index": 1, + "type": "touch_sent" + }, + { + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch2", + "status": "ok" + }, + "touch_index": 2, + "type": "touch_sent" + } + ], + "engagement_projection": { + "aggregate_id": "seq-acme-001:contact-jane", + "operation": "read_projection", + "operation_result": { + "operation": "read_projection", + "receipt_ref": "runx:receipt:fixture-read-happy", + "status": "ok" + }, + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1", + "version": 7 + }, + "escalation": { + "lane": null, + "required": false + }, + "next_touch_packet": { + "audience": "jane@example.invalid", + "audience_role": "buyer", + "channel": "email", + "content_digest": "sha256:touch3", + "dispatch": { + "consequence": "separate_governed_run_required", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "named_run": "send-as", + "this_skill_sends": false + }, + "principal": "sales-dev", + "schema": "runx.outreach.next_touch.v1", + "send_class": "outreach", + "touch_index": 3 + }, + "observations": [ + { + "aggregate_id": "seq-acme-001:contact-jane", + "operation_result": { + "operation": "read_projection", + "receipt_ref": "runx:receipt:fixture-read-happy", + "status": "ok" + }, + "store_id": "runx.outreach.sequence_state.v1", + "type": "engagement_projection_read", + "version": 7 + }, + { + "events": [ + { + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch1", + "status": "ok" + }, + "touch_index": 1, + "type": "touch_sent" + }, + { + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch2", + "status": "ok" + }, + "touch_index": 2, + "type": "touch_sent" + } + ], + "type": "engagement_events_examined" + }, + { + "current_touch_index": 2, + "type": "sequence_position" + }, + { + "eligible": true, + "reason": "next_touch_due", + "type": "eligibility_verdict" + }, + { + "after_version": 8, + "before_version": 7, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "operation_result": { + "after_version": 8, + "before_version": 7, + "operation": "append_event", + "status": "planned" + }, + "type": "append_event" + }, + { + "channel": "email", + "content_digest": "sha256:touch3", + "index": 3, + "send_class": "outreach", + "type": "next_touch" + } + ], + "schema": "runx.outreach.sequencer.v1", + "status": "sealed" + } + }, + "payload": { + "append_event": { + "after_version": 8, + "aggregate_id": "seq-acme-001:contact-jane", + "before_version": 7, + "cas": "expected_version", + "event": { + "decision_id": "decision_8920a12b7a72", + "dispatch_idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "eligible": true, + "prior_touch_index": 2, + "touch_index": 3, + "type": "outreach.next_touch_decided" + }, + "expected_version": 7, + "gated": false, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "operation": "append_event", + "operation_result": { + "after_version": 8, + "before_version": 7, + "operation": "append_event", + "status": "planned" + }, + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1" + }, + "decision": { + "eligible": true, + "reason": "next_touch_due" + }, + "engagement_events": [ + { + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch1", + "status": "ok" + }, + "touch_index": 1, + "type": "touch_sent" + }, + { + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch2", + "status": "ok" + }, + "touch_index": 2, + "type": "touch_sent" + } + ], + "engagement_projection": { + "aggregate_id": "seq-acme-001:contact-jane", + "operation": "read_projection", + "operation_result": { + "operation": "read_projection", + "receipt_ref": "runx:receipt:fixture-read-happy", + "status": "ok" + }, + "store": "registry:runx/data-store@0.1.2", + "store_id": "runx.outreach.sequence_state.v1", + "version": 7 + }, + "escalation": { + "lane": null, + "required": false + }, + "next_touch_packet": { + "audience": "jane@example.invalid", + "audience_role": "buyer", + "channel": "email", + "content_digest": "sha256:touch3", + "dispatch": { + "consequence": "separate_governed_run_required", + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3:dispatch", + "named_run": "send-as", + "this_skill_sends": false + }, + "principal": "sales-dev", + "schema": "runx.outreach.next_touch.v1", + "send_class": "outreach", + "touch_index": 3 + }, + "observations": [ + { + "aggregate_id": "seq-acme-001:contact-jane", + "operation_result": { + "operation": "read_projection", + "receipt_ref": "runx:receipt:fixture-read-happy", + "status": "ok" + }, + "store_id": "runx.outreach.sequence_state.v1", + "type": "engagement_projection_read", + "version": 7 + }, + { + "events": [ + { + "occurred_at": "2026-06-20T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch1", + "status": "ok" + }, + "touch_index": 1, + "type": "touch_sent" + }, + { + "occurred_at": "2026-06-25T12:00:00Z", + "operation_result": { + "operation": "append_event", + "receipt_ref": "runx:receipt:fixture-touch2", + "status": "ok" + }, + "touch_index": 2, + "type": "touch_sent" + } + ], + "type": "engagement_events_examined" + }, + { + "current_touch_index": 2, + "type": "sequence_position" + }, + { + "eligible": true, + "reason": "next_touch_due", + "type": "eligibility_verdict" + }, + { + "after_version": 8, + "before_version": 7, + "idempotency_key": "outreach-sequencer:seq-acme-001:contact-jane:touch3", + "operation_result": { + "after_version": 8, + "before_version": 7, + "operation": "append_event", + "status": "planned" + }, + "type": "append_event" + }, + { + "channel": "email", + "content_digest": "sha256:touch3", + "index": 3, + "send_class": "outreach", + "type": "next_touch" + } + ], + "schema": "runx.outreach.sequencer.v1", + "status": "sealed" + }, + "receipt": { + "acts": [ + { + "artifact_refs": [], + "closure": { + "closed_at": "2026-06-30T16:27:04.909Z", + "disposition": "closed", + "reason_code": "process_exit", + "summary": "cli-tool exited successfully" + }, + "criterion_bindings": [ + { + "criterion_id": "process_exit", + "evidence_refs": [], + "status": "verified", + "summary": "cli-tool exited successfully", + "verification_refs": [] + } + ], + "form": "observation", + "id": "act_judge", + "intent": { + "constraints": [], + "derived_from": [], + "legitimacy": "Runtime graph execution was admitted by the local harness", + "purpose": "Run graph step judge", + "success_criteria": [ + { + "criterion_id": "process_exit", + "required": true, + "statement": "cli-tool exits successfully" + } + ] + }, + "source_refs": [], + "summary": "Executed graph step judge", + "target_refs": [] + } + ], + "authority": { + "actor_ref": { + "type": "principal", + "uri": "runx:principal:local_runtime" + }, + "attenuation": { + "parent_authority_ref": null, + "subset_proof": null + }, + "authority_proof_refs": [], + "enforcement": { + "profile_hash": "sha256:635fdb0a7eef93911e3c6bd0b25b5e635edba32e0259133042b3a5f1fb19394e", + "redaction_refs": [], + "setup_refs": [], + "teardown_refs": [] + }, + "grant_refs": [], + "scope_refs": [], + "terms": [] + }, + "canonicalization": "runx.receipt.c14n.v1", + "created_at": "2026-06-30T16:27:04.909Z", + "decisions": [ + { + "artifact_refs": [], + "choice": "open", + "closure": null, + "decision_id": "dec_judge", + "inputs": { + "opportunity_refs": [], + "selection_ref": null, + "signal_refs": [], + "target_ref": null + }, + "justification": { + "evidence_refs": [], + "summary": "runtime graph planner selected this node" + }, + "proposed_intent": { + "constraints": [], + "derived_from": [], + "legitimacy": "Local graph execution requested this node", + "purpose": "Open runtime node judge", + "success_criteria": [] + }, + "selected_act_id": "act_judge", + "selected_harness_ref": null + } + ], + "digest": "sha256:68f3e36a9da83b7d7111c951894e4cd9d1246c61c48212363b5b72674d82eec5", + "id": "sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "idempotency": { + "content_hash": "sha256:run_judge_bc5b2b090582-judge-content", + "intent_key": "sha256:run_judge_bc5b2b090582-judge-intent", + "trigger_fingerprint": "sha256:run_judge_bc5b2b090582-judge-trigger" + }, + "issuer": { + "kid": "runx-runtime-prod-fixture-key", + "public_key_sha256": "sha256:3097e2dee2cb4a34b53840cdb705aed71067c36f68db0e0f559c3f3fa043315f", + "type": "hosted" + }, + "lineage": { + "children": [], + "sync": [] + }, + "schema": "runx.receipt.v1", + "seal": { + "closed_at": "2026-06-30T16:27:04.909Z", + "criteria": [ + { + "criterion_id": "process_exit", + "evidence_refs": [], + "status": "verified", + "summary": "cli-tool exited successfully", + "verification_refs": [] + } + ], + "disposition": "closed", + "last_observed_at": "2026-06-30T16:27:04.909Z", + "reason_code": "process_closed", + "summary": "cli-tool judge completed" + }, + "signals": [], + "signature": { + "alg": "Ed25519", + "value": "base64:D3Y2cs-jScww6WPqZN0SeIfpOxH-V2EqIQAQRmArYLQsioDQD7Y9dzca_8yISFO7iwrPfqDK-URnRburVnTEBQ" + }, + "subject": { + "commitments": [], + "kind": "skill", + "ref": { + "type": "harness", + "uri": "hrn_run_judge_bc5b2b090582_judge" + } + } + }, + "receipt_id": "sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "registry_provenance": { + "digest": "sha256:53610c96d9f26634e7b1887fe0770d80007d7535960da60be9b959936eef839e", + "profile_digest": "sha256:f5e1afd4c63da3dfd5292264e10cb03cf2ca84612927277136932c79b8dc021b", + "registry_key_id": "runx-registry-ed25519-v1", + "registry_source": "remote https://api.runx.ai", + "registry_source_fingerprint": "ba1ac16b631195fd", + "skill_id": "vidshidden/outreach-sequencer", + "trust_state": "trusted", + "trust_tier": "community", + "version": "sha-8248a4585211" + }, + "run_id": "run_judge_bc5b2b090582", + "schema": "runx.skill_run.v1", + "skill_name": "outreach-sequencer", + "status": "sealed" +} diff --git a/skills/outreach-sequencer/evidence/dogfood-receipt.json b/skills/outreach-sequencer/evidence/dogfood-receipt.json new file mode 100644 index 000000000..abc5011b5 --- /dev/null +++ b/skills/outreach-sequencer/evidence/dogfood-receipt.json @@ -0,0 +1 @@ +{"schema":"runx.receipt_store_index.v1","generated_at":"1782836824916802488","entries":[{"receipt_id":"sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec","file_name":"sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec.json","created_at":"2026-06-30T16:27:04.909Z"}]} \ No newline at end of file diff --git a/skills/outreach-sequencer/evidence/dogfood-verify.json b/skills/outreach-sequencer/evidence/dogfood-verify.json new file mode 100644 index 000000000..17796dd7c --- /dev/null +++ b/skills/outreach-sequencer/evidence/dogfood-verify.json @@ -0,0 +1,15 @@ +{ + "receipt_dir": "/home/runner/work/runx/runx/skills/outreach-sequencer/evidence/dogfood-receipts", + "signature_mode": "production", + "trees": [ + { + "root_receipt_id": "sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "receipt_count": 1, + "parent_missing": null, + "valid": true, + "findings": [] + } + ], + "unreadable_files": [], + "valid": true +} diff --git a/skills/outreach-sequencer/evidence/evidence.json b/skills/outreach-sequencer/evidence/evidence.json index f318f55f2..a28981267 100644 --- a/skills/outreach-sequencer/evidence/evidence.json +++ b/skills/outreach-sequencer/evidence/evidence.json @@ -55,15 +55,14 @@ } }, "command": "runx skill vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai --json -R skills/outreach-sequencer/evidence/dogfood-receipts", - "status": "pending_ubuntu", - "receipt_ref": "pending-ubuntu-dogfood", + "status": "sealed", + "receipt_ref": "runx:receipt:sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "run_id": "run_judge_bc5b2b090582", "verify_verdict": { - "valid": false, - "signature_mode": null, - "root_receipt_id": null, - "findings": [ - "Ubuntu dogfood verify has not been recorded yet." - ] + "valid": true, + "signature_mode": "production", + "root_receipt_id": "sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "findings": [] }, "harness_cases": [ { @@ -220,15 +219,14 @@ }, { "type": "dogfood_verify", - "receipt_ref": "pending-ubuntu-dogfood", + "receipt_ref": "runx:receipt:sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", "verify_verdict": { - "valid": false, - "signature_mode": null, - "root_receipt_id": null, - "findings": [ - "Ubuntu dogfood verify has not been recorded yet." - ] - } + "valid": true, + "signature_mode": "production", + "root_receipt_id": "sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "findings": [] + }, + "run_id": "run_judge_bc5b2b090582" }, { "type": "artifact_urls", diff --git a/skills/outreach-sequencer/evidence/report.md b/skills/outreach-sequencer/evidence/report.md index 2e66e0fd6..65472eb0f 100644 --- a/skills/outreach-sequencer/evidence/report.md +++ b/skills/outreach-sequencer/evidence/report.md @@ -19,8 +19,8 @@ - Hosted harness status: passed, cases happy_next_touch, stop_replied, missing_state_needs_agent. - Clean install command: `runx add vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai`. - Dogfood command: `runx skill vidshidden/outreach-sequencer@sha-8248a4585211 --registry https://api.runx.ai --json -R skills/outreach-sequencer/evidence/dogfood-receipts`. -- Dogfood receipt: pending-ubuntu-dogfood. -- runx verify verdict: pending or not valid yet; signature mode pending. +- Dogfood receipt: runx:receipt:sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec. +- runx verify verdict: valid; signature mode production. - Windows local dogfood status: failure; expected receipt-store issue is recorded in dogfood-output-windows.json. ## Behavior diff --git a/skills/outreach-sequencer/evidence/verification.json b/skills/outreach-sequencer/evidence/verification.json index 9216c7094..e54ec94cc 100644 --- a/skills/outreach-sequencer/evidence/verification.json +++ b/skills/outreach-sequencer/evidence/verification.json @@ -31,9 +31,9 @@ "missing_state_needs_agent": "passed" }, "dogfood": { - "status": "pending_ubuntu", - "receipt_ref": "pending-ubuntu-dogfood", - "verify_valid": false, + "status": "sealed", + "receipt_ref": "runx:receipt:sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec", + "verify_valid": true, "windows_status": "failure", "windows_error": "receipt store is unreadable: 参数错误。 (os error 87)", "ubuntu_actions_workflow": ".github/workflows/outreach-sequencer-dogfood.yml", @@ -43,5 +43,5 @@ "skills/outreach-sequencer/evidence/dogfood-receipt.json" ] }, - "receipt_ref": "pending-ubuntu-dogfood" + "receipt_ref": "runx:receipt:sha256:07dc416636da29e35ec4b3672001506737990080c11e76954a2f84da5e4e49ec" } From 8232e4ef9a6d03759e1895ee602cc968fbeea093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=AA=E5=AE=87?= <127947552+VidsHidden@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:29:06 +0800 Subject: [PATCH 3/5] Fill outreach-sequencer PR URL in evidence [skip dogfood] --- skills/outreach-sequencer/evidence/report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/outreach-sequencer/evidence/report.md b/skills/outreach-sequencer/evidence/report.md index 65472eb0f..9d28c768f 100644 --- a/skills/outreach-sequencer/evidence/report.md +++ b/skills/outreach-sequencer/evidence/report.md @@ -4,7 +4,7 @@ - Package: vidshidden/outreach-sequencer@sha-8248a4585211 - Public URL: https://runx.ai/x/vidshidden/outreach-sequencer@sha-8248a4585211 -- PR URL: https://github.com/runxhq/runx/pull/PLACEHOLDER +- PR URL: https://github.com/runxhq/runx/pull/193 - Source URL: https://github.com/VidsHidden/runx/tree/outreach-sequencer/skills/outreach-sequencer - Raw X.yaml: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/X.yaml - Raw SKILL.md: https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/SKILL.md From 87c45c5e88d72e568d248b787ebbaa86dccc4234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=AA=E5=AE=87?= <127947552+VidsHidden@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:29:10 +0800 Subject: [PATCH 4/5] Fill outreach-sequencer PR URL in evidence [skip dogfood] --- skills/outreach-sequencer/evidence/evidence.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/outreach-sequencer/evidence/evidence.json b/skills/outreach-sequencer/evidence/evidence.json index a28981267..fc6794a11 100644 --- a/skills/outreach-sequencer/evidence/evidence.json +++ b/skills/outreach-sequencer/evidence/evidence.json @@ -231,7 +231,7 @@ { "type": "artifact_urls", "public_url": "https://runx.ai/x/vidshidden/outreach-sequencer@sha-8248a4585211", - "pr_url": "https://github.com/runxhq/runx/pull/PLACEHOLDER", + "pr_url": "https://github.com/runxhq/runx/pull/193", "source_url": "https://github.com/VidsHidden/runx/tree/outreach-sequencer/skills/outreach-sequencer", "x_yaml": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/X.yaml", "skill_md": "https://raw.githubusercontent.com/VidsHidden/runx/outreach-sequencer/skills/outreach-sequencer/SKILL.md", From 8f5eeb8e093a58cc64fab05ec2a2d1e42d4ae712 Mon Sep 17 00:00:00 2001 From: kam Date: Wed, 1 Jul 2026 04:27:15 +1000 Subject: [PATCH 5/5] fix: update outreach sequencer skill metadata --- crates/runx-cli/src/official_skills.rs | 5 +++++ packages/cli/src/official-skills.lock.json | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/crates/runx-cli/src/official_skills.rs b/crates/runx-cli/src/official_skills.rs index a3c4ff239..8195071a0 100644 --- a/crates/runx-cli/src/official_skills.rs +++ b/crates/runx-cli/src/official_skills.rs @@ -205,6 +205,11 @@ pub(crate) const OFFICIAL_SKILLS: &[OfficialSkillLockEntry] = &[ version: "sha-a2f4840047ee", digest: "f32f21b6ed6c03f5623bc98f2823365ebcabc721cedf64e8a67c556613a35f59", }, + OfficialSkillLockEntry { + skill_id: "runx/outreach-sequencer", + version: "sha-4fa79af84905", + digest: "e1860ab37decf86bcacd30b8665f7b91bde369c7f503a73ba38a3d8dc7495ae0", + }, OfficialSkillLockEntry { skill_id: "runx/overlay-generator", version: "sha-b5dc11a7088d", diff --git a/packages/cli/src/official-skills.lock.json b/packages/cli/src/official-skills.lock.json index aa39784c0..dcf6a7438 100644 --- a/packages/cli/src/official-skills.lock.json +++ b/packages/cli/src/official-skills.lock.json @@ -272,6 +272,13 @@ "catalog_visibility": "public", "catalog_role": "canonical" }, + { + "skill_id": "runx/outreach-sequencer", + "version": "sha-4fa79af84905", + "digest": "e1860ab37decf86bcacd30b8665f7b91bde369c7f503a73ba38a3d8dc7495ae0", + "catalog_visibility": "public", + "catalog_role": "canonical" + }, { "skill_id": "runx/overlay-generator", "version": "sha-b5dc11a7088d",