@etiquekit/etq 0.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +28 -0
- package/QuickStart.md +86 -0
- package/README.md +77 -9
- package/bin/etiquette +1150 -0
- package/bin/etiquette-core +313 -0
- package/docs/ARCHITECTURE.md +100 -0
- package/docs/CODEX_CLIENT_COMPATIBILITY.md +83 -0
- package/docs/CONCEPTS.md +128 -0
- package/docs/CONCEPT_STATUS.md +67 -0
- package/docs/CORE_PROFILE.md +70 -0
- package/docs/LANE_PROVISIONING.md +157 -0
- package/docs/README.md +29 -0
- package/docs/RELEASE_SURFACE_AUDIT.md +156 -0
- package/docs/SEAT_DISCIPLINE.md +116 -0
- package/docs/SEAT_PROVISIONING.md +100 -0
- package/docs/TEAM_HANDOFF.md +190 -0
- package/docs/WORKTREE_QOL.md +92 -0
- package/docs/contracts/ledger-entry/README.md +83 -0
- package/docs/contracts/ledger-entry/ledger-entry.v0.1.md +116 -0
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +90 -0
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +123 -0
- package/docs/contracts/ledger-entry/ledger-query-cli.v0.1.md +64 -0
- package/package.json +42 -11
- package/scripts/install.sh +144 -0
- package/scripts/uninstall.sh +58 -0
- package/templates/etiquette-vanilla-v0/README.md +69 -0
- package/templates/etiquette-vanilla-v0/bin/stamp-vanilla +33 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/.llm-substrate/HANDOFF.md +22 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +16 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/http.sh +121 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/sanitize.sh +191 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/state.sh +67 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/compact-local-events +71 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/post-tool-use +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-end +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-start +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/stop +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/user-prompt-submit +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/local-event +53 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-receipt +143 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-telemetry +112 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-consume-pass +115 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +20 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-init +86 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-post-readiness +171 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/session-init +132 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/ledger/LEDGER.md +7 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/playbacks/PLAYBACK_TEMPLATE.md +31 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/receipts/.gitkeep +1 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/tasks/CONTROL-001.yaml +13 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/README.md +6 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/bin/kernel-doctor +11 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/playback.v0.md +37 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/receipt.v0.md +25 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/seat-readiness.v0.md +22 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/task.v0.md +18 -0
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +43 -0
- package/templates/github-actions/README.md +30 -0
- package/templates/github-actions/w1-webhook-wake-canary.yml +111 -0
- package/templates/hosted-receiver/README.md +41 -0
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
- package/templates/seat-packs-v0/README.md +72 -0
- package/templates/seat-packs-v0/bin/provision-seat +70 -0
- package/templates/seat-packs-v0/source/claude-code-seat/.llm-substrate/HANDOFF.md +24 -0
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/claude-code-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/codex-seat/.llm-substrate/HANDOFF.md +25 -0
- package/templates/seat-packs-v0/source/codex-seat/README.md +22 -0
- package/templates/seat-packs-v0/source/codex-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/gemini-seat/.llm-substrate/HANDOFF.md +23 -0
- package/templates/seat-packs-v0/source/gemini-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/gemini-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/ollama-seat/.llm-substrate/HANDOFF.md +24 -0
- package/templates/seat-packs-v0/source/ollama-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/ollama-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/openrouter-seat/.llm-substrate/HANDOFF.md +24 -0
- package/templates/seat-packs-v0/source/openrouter-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/openrouter-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/validate-seat-packs.sh +27 -0
- package/bin/etq +0 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "workspace-secure-profile.v0",
|
|
3
|
+
"workspace_id": "vanilla-control-seat",
|
|
4
|
+
"mode": "open",
|
|
5
|
+
"allowed_modes": [
|
|
6
|
+
"open",
|
|
7
|
+
"screening-warn",
|
|
8
|
+
"screening-enforced",
|
|
9
|
+
"enterprise-reactor"
|
|
10
|
+
],
|
|
11
|
+
"mode_meanings": {
|
|
12
|
+
"open": "No access-assurance gate is required. Etiquette core remains local and unauthenticated.",
|
|
13
|
+
"screening-warn": "Restricted-seat claims are checked and reported, but missing or failed claims do not block local entry.",
|
|
14
|
+
"screening-enforced": "Restricted-seat claims are required before routing or entering restricted seats.",
|
|
15
|
+
"enterprise-reactor": "screening-enforced plus an optional tighten-only reactor for revocation sync, badge refresh, kill-switch writes, and outbox drain."
|
|
16
|
+
},
|
|
17
|
+
"identity_model": {
|
|
18
|
+
"authenticate": "operator_principals",
|
|
19
|
+
"assign_to": "seat_ids",
|
|
20
|
+
"authorize": "per_action_grants",
|
|
21
|
+
"workspace_role": "policy_scope",
|
|
22
|
+
"linear_role": "external_backlog_projection_only"
|
|
23
|
+
},
|
|
24
|
+
"claim_source": {
|
|
25
|
+
"kind": "none-required-for-open-mode",
|
|
26
|
+
"live_replacement": "Supabase Auth plus derived Persona and Scandit claim export",
|
|
27
|
+
"raw_pii_allowed_in_repo": false
|
|
28
|
+
},
|
|
29
|
+
"reactor": {
|
|
30
|
+
"required_for_mode": "enterprise-reactor",
|
|
31
|
+
"default_enabled": false,
|
|
32
|
+
"tighten_only": true,
|
|
33
|
+
"may_write": [
|
|
34
|
+
"kill_switch_entries",
|
|
35
|
+
"expired_assurance_markers",
|
|
36
|
+
"badge_refresh_cache",
|
|
37
|
+
"sync_outbox"
|
|
38
|
+
],
|
|
39
|
+
"must_not": [
|
|
40
|
+
"grant_access",
|
|
41
|
+
"close_work",
|
|
42
|
+
"merge",
|
|
43
|
+
"rewrite_ledger",
|
|
44
|
+
"authorize_actions"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"authority_boundary": {
|
|
48
|
+
"profile_can_authorize_work": false,
|
|
49
|
+
"profile_can_grant_seat": false,
|
|
50
|
+
"profile_can_merge_or_close": false,
|
|
51
|
+
"reactor_can_grant_or_loosen_access": false
|
|
52
|
+
}
|
|
53
|
+
}
|
package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/playbacks/PLAYBACK_TEMPLATE.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Manual Playback Template
|
|
2
|
+
|
|
3
|
+
```yaml
|
|
4
|
+
playback_id:
|
|
5
|
+
timeframe:
|
|
6
|
+
scope:
|
|
7
|
+
operator_principal:
|
|
8
|
+
execution_seat:
|
|
9
|
+
inputs:
|
|
10
|
+
receipts: []
|
|
11
|
+
ledger_refs: []
|
|
12
|
+
commit_refs: []
|
|
13
|
+
decisions:
|
|
14
|
+
- decision:
|
|
15
|
+
evidence_refs: []
|
|
16
|
+
lessons:
|
|
17
|
+
- lesson:
|
|
18
|
+
evidence_refs: []
|
|
19
|
+
deferred:
|
|
20
|
+
- item:
|
|
21
|
+
reason:
|
|
22
|
+
proposed_actions:
|
|
23
|
+
- action:
|
|
24
|
+
type: no_change | docs_patch | memory_save | controlled_drill
|
|
25
|
+
evidence_refs: []
|
|
26
|
+
operator_decision_required:
|
|
27
|
+
controlled_drill: true
|
|
28
|
+
batch_approval_allowed: false
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use this after meaningful workflow change. Keep it evidence-bound: cite receipts, ledger rows, commits, and validation output. Do not treat chat as proof.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
id: CONTROL-001
|
|
2
|
+
status: open
|
|
3
|
+
owner: control-seat
|
|
4
|
+
mode: docs-only
|
|
5
|
+
title: Prove vanilla Etiquette startup
|
|
6
|
+
purpose:
|
|
7
|
+
- validate the control seat without provisioning runtime-specific seats
|
|
8
|
+
validation:
|
|
9
|
+
- sh bin/seat-doctor
|
|
10
|
+
expected_return:
|
|
11
|
+
- readiness receipt
|
|
12
|
+
- decision whether more seats are needed
|
|
13
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
test -f "$ROOT/contracts/receipt.v0.md"
|
|
7
|
+
test -f "$ROOT/contracts/playback.v0.md"
|
|
8
|
+
test -f "$ROOT/contracts/seat-readiness.v0.md"
|
|
9
|
+
test -f "$ROOT/contracts/task.v0.md"
|
|
10
|
+
|
|
11
|
+
echo "etiquette-kernel: ok"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Playback V0
|
|
2
|
+
|
|
3
|
+
A playback is a manual review of evidence after meaningful workflow change.
|
|
4
|
+
It interprets receipts; it does not replace receipts.
|
|
5
|
+
|
|
6
|
+
```yaml
|
|
7
|
+
playback_id:
|
|
8
|
+
timeframe:
|
|
9
|
+
scope:
|
|
10
|
+
operator_principal:
|
|
11
|
+
execution_seat:
|
|
12
|
+
inputs:
|
|
13
|
+
receipts: []
|
|
14
|
+
ledger_refs: []
|
|
15
|
+
commit_refs: []
|
|
16
|
+
decisions:
|
|
17
|
+
- decision:
|
|
18
|
+
evidence_refs: []
|
|
19
|
+
lessons:
|
|
20
|
+
- lesson:
|
|
21
|
+
evidence_refs: []
|
|
22
|
+
deferred:
|
|
23
|
+
- item:
|
|
24
|
+
reason:
|
|
25
|
+
proposed_actions:
|
|
26
|
+
- action:
|
|
27
|
+
type: no_change | docs_patch | memory_save | controlled_drill
|
|
28
|
+
evidence_refs: []
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- Receipts are evidence.
|
|
34
|
+
- Playback interprets evidence.
|
|
35
|
+
- Controlled drills require one operator approval per drill.
|
|
36
|
+
- Advisory sources may be referenced, but they are not proof.
|
|
37
|
+
- Automation waits until manual playback shows a repeated gap.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Receipt V0
|
|
2
|
+
|
|
3
|
+
Every meaningful task returns a compact receipt.
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
task_id:
|
|
7
|
+
operator_principal:
|
|
8
|
+
execution_seat:
|
|
9
|
+
status: success | partial | blocked | failed
|
|
10
|
+
sources_read: []
|
|
11
|
+
files_changed: []
|
|
12
|
+
validation: []
|
|
13
|
+
risks: []
|
|
14
|
+
next_owner:
|
|
15
|
+
# Optional when compacting local events into a receipt:
|
|
16
|
+
compaction:
|
|
17
|
+
scope: local-only
|
|
18
|
+
event_log:
|
|
19
|
+
event_count:
|
|
20
|
+
first_seq:
|
|
21
|
+
last_seq:
|
|
22
|
+
last_action:
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Receipts are durable. Chat is not canonical evidence.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Seat Readiness V0
|
|
2
|
+
|
|
3
|
+
Each seat reports capability before receiving work.
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
seat:
|
|
7
|
+
seat_type: control_seat | execution_seat | review_seat | kernel_maintainer | seat_maintainer
|
|
8
|
+
execution_surface:
|
|
9
|
+
declared_by: operator_principal | config
|
|
10
|
+
capability_source: config | local_validation
|
|
11
|
+
operational_status: operational | degraded | blocked | offline
|
|
12
|
+
worker_capacity:
|
|
13
|
+
parallel_support: true | false
|
|
14
|
+
supported_strategies: []
|
|
15
|
+
blocked_strategies: []
|
|
16
|
+
validation: []
|
|
17
|
+
constraints: []
|
|
18
|
+
last_checked_at:
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Unsupported strategies hard-block only when `strategy_policy: required`.
|
|
22
|
+
Agent confidence is not proof of capability. Capability is operator/config declared and backed by local validation.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Task V0
|
|
2
|
+
|
|
3
|
+
A task says what to do, what may change, and how to validate completion.
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
task_id:
|
|
7
|
+
owner:
|
|
8
|
+
mode: read-only | docs-only | implementation | review
|
|
9
|
+
repo:
|
|
10
|
+
allowed_writes: []
|
|
11
|
+
forbidden_sources: []
|
|
12
|
+
validation: []
|
|
13
|
+
stop_conditions: []
|
|
14
|
+
expected_return: []
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Tasks should be small enough that a receipt can prove what happened.
|
|
18
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
|
|
6
|
+
test -f "$ROOT/README.md"
|
|
7
|
+
test -d "$ROOT/source/etiquette-kernel"
|
|
8
|
+
test -d "$ROOT/source/control-seat"
|
|
9
|
+
grep -Fq ".etiquette/events/*.ndjson" "$ROOT/source/control-seat/.gitignore"
|
|
10
|
+
grep -q '"schema": "workspace-secure-profile.v0"' "$ROOT/source/control-seat/docs/work/access/workspace-secure-profile.v0.json"
|
|
11
|
+
grep -q '"mode": "open"' "$ROOT/source/control-seat/docs/work/access/workspace-secure-profile.v0.json"
|
|
12
|
+
grep -q "enterprise-reactor" "$ROOT/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md"
|
|
13
|
+
grep -q "Controlled drills require one operator approval per drill" "$ROOT/source/etiquette-kernel/contracts/playback.v0.md"
|
|
14
|
+
grep -q "batch_approval_allowed: false" "$ROOT/source/control-seat/docs/work/playbacks/PLAYBACK_TEMPLATE.md"
|
|
15
|
+
|
|
16
|
+
sh "$ROOT/source/etiquette-kernel/bin/kernel-doctor"
|
|
17
|
+
sh "$ROOT/source/control-seat/bin/seat-doctor"
|
|
18
|
+
sh "$ROOT/source/control-seat/bin/access-assurance-check" | grep -q "mode=open"
|
|
19
|
+
|
|
20
|
+
tmp_root=$(mktemp -d "${TMPDIR:-/tmp}/etiquette-vanilla-compact.XXXXXX")
|
|
21
|
+
trap 'rm -rf "$tmp_root"' EXIT
|
|
22
|
+
|
|
23
|
+
cp -R "$ROOT/source/control-seat/." "$tmp_root/control-seat/"
|
|
24
|
+
sh "$tmp_root/control-seat/bin/seat-init" --execution-seat control-seat >/dev/null
|
|
25
|
+
sh "$tmp_root/control-seat/bin/access-assurance-check" | grep -q "mode=open"
|
|
26
|
+
sh "$tmp_root/control-seat/bin/session-init" --task-id SESSION-INIT --operator local-operator >/tmp/session-init.out
|
|
27
|
+
test -f "$tmp_root/control-seat/.etiquette/state/session.json"
|
|
28
|
+
grep -q "session_opened" "$tmp_root/control-seat/docs/work/ledger/LEDGER.md"
|
|
29
|
+
sh "$tmp_root/control-seat/bin/local-event" CONTROL-001 control-seat started "started local proof"
|
|
30
|
+
sh "$tmp_root/control-seat/bin/local-event" CONTROL-001 control-seat validated "ran seat doctor"
|
|
31
|
+
sh "$tmp_root/control-seat/bin/local-event" CONTROL-001 control-seat completed "ready to compact"
|
|
32
|
+
sh "$tmp_root/control-seat/bin/compact-local-events" CONTROL-001 success
|
|
33
|
+
|
|
34
|
+
receipt="$tmp_root/control-seat/docs/work/receipts/CONTROL-001-receipt.yaml"
|
|
35
|
+
test -f "$receipt"
|
|
36
|
+
grep -q "scope: local-only" "$receipt"
|
|
37
|
+
grep -q "operator_principal: local-operator" "$receipt"
|
|
38
|
+
grep -q "execution_seat: control-seat" "$receipt"
|
|
39
|
+
grep -q "event_count: 3" "$receipt"
|
|
40
|
+
grep -q "first_seq: 2" "$receipt"
|
|
41
|
+
grep -q "last_seq: 4" "$receipt"
|
|
42
|
+
|
|
43
|
+
echo "etiquette vanilla: ok"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# GitHub Actions Templates
|
|
2
|
+
|
|
3
|
+
These templates are opt-in canaries. Copy one into a disposable repository
|
|
4
|
+
before using it on a production repo.
|
|
5
|
+
|
|
6
|
+
## W1 Webhook Wake Canary
|
|
7
|
+
|
|
8
|
+
`w1-webhook-wake-canary.yml` proves the W1 receiver loop inside GitHub Actions:
|
|
9
|
+
|
|
10
|
+
1. read the GitHub push event payload;
|
|
11
|
+
2. sign it with a canary HMAC secret;
|
|
12
|
+
3. call `etiquette notify webhook github`;
|
|
13
|
+
4. verify the emitted wake points at the pushed commit;
|
|
14
|
+
5. fetch the commit from Git before treating the wake as useful.
|
|
15
|
+
|
|
16
|
+
Required secret:
|
|
17
|
+
|
|
18
|
+
- `ETIQUETTE_GITHUB_WEBHOOK_SECRET`: canary HMAC secret used only for this proof.
|
|
19
|
+
|
|
20
|
+
Choose one CLI source:
|
|
21
|
+
|
|
22
|
+
- set repository variable `ETIQUETTE_CLI` to an installed command such as
|
|
23
|
+
`etiquette`; or
|
|
24
|
+
- set repository variable `ETIQUETTE_SOURCE_REPOSITORY` and secret
|
|
25
|
+
`ETIQUETTE_REPO_TOKEN` so the workflow can check out the Etiquette source
|
|
26
|
+
read-only.
|
|
27
|
+
|
|
28
|
+
Boundary: the workflow emits a pointer-only wake artifact and step summary. It
|
|
29
|
+
does not install a public webhook, call AWS/SNS/SQS, post to Slack, run remote
|
|
30
|
+
agents, merge, close, publish, or grant authority.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
name: Etiquette W1 webhook wake canary
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- "**"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
ETIQUETTE_CLI: ${{ vars.ETIQUETTE_CLI }}
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
w1-webhook-wake:
|
|
16
|
+
name: Signed push payload to pointer-only wake
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout canary repo
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 1
|
|
23
|
+
|
|
24
|
+
- name: Setup Bun
|
|
25
|
+
if: ${{ env.ETIQUETTE_CLI == '' }}
|
|
26
|
+
uses: oven-sh/setup-bun@v2
|
|
27
|
+
|
|
28
|
+
- name: Check Etiquette source configuration
|
|
29
|
+
if: ${{ env.ETIQUETTE_CLI == '' }}
|
|
30
|
+
env:
|
|
31
|
+
ETIQUETTE_SOURCE_REPOSITORY: ${{ vars.ETIQUETTE_SOURCE_REPOSITORY }}
|
|
32
|
+
run: |
|
|
33
|
+
test -n "${ETIQUETTE_SOURCE_REPOSITORY:-}" || {
|
|
34
|
+
echo "Set ETIQUETTE_CLI, or set ETIQUETTE_SOURCE_REPOSITORY plus ETIQUETTE_REPO_TOKEN."
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
- name: Checkout Etiquette source
|
|
39
|
+
if: ${{ env.ETIQUETTE_CLI == '' }}
|
|
40
|
+
uses: actions/checkout@v4
|
|
41
|
+
with:
|
|
42
|
+
repository: ${{ vars.ETIQUETTE_SOURCE_REPOSITORY }}
|
|
43
|
+
ref: main
|
|
44
|
+
path: .etq/etiquette
|
|
45
|
+
token: ${{ secrets.ETIQUETTE_REPO_TOKEN }}
|
|
46
|
+
|
|
47
|
+
- name: Install Etiquette source dependencies
|
|
48
|
+
if: ${{ env.ETIQUETTE_CLI == '' }}
|
|
49
|
+
working-directory: .etq/etiquette
|
|
50
|
+
run: bun install --frozen-lockfile
|
|
51
|
+
|
|
52
|
+
- name: Verify signed wake and Git ref
|
|
53
|
+
env:
|
|
54
|
+
ETIQUETTE_GITHUB_WEBHOOK_SECRET: ${{ secrets.ETIQUETTE_GITHUB_WEBHOOK_SECRET }}
|
|
55
|
+
run: |
|
|
56
|
+
set -eu
|
|
57
|
+
test -n "${ETIQUETTE_GITHUB_WEBHOOK_SECRET:-}" || {
|
|
58
|
+
echo "ETIQUETTE_GITHUB_WEBHOOK_SECRET is required."
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ETQ_CMD="${ETIQUETTE_CLI:-bun $GITHUB_WORKSPACE/.etq/etiquette/packages/control/src/cli.ts}"
|
|
63
|
+
SIGNATURE="$(node <<'NODE'
|
|
64
|
+
const crypto = require('node:crypto');
|
|
65
|
+
const fs = require('node:fs');
|
|
66
|
+
const payload = fs.readFileSync(process.env.GITHUB_EVENT_PATH);
|
|
67
|
+
const secret = process.env.ETIQUETTE_GITHUB_WEBHOOK_SECRET;
|
|
68
|
+
process.stdout.write(`sha256=${crypto.createHmac('sha256', secret).update(payload).digest('hex')}`);
|
|
69
|
+
NODE
|
|
70
|
+
)"
|
|
71
|
+
|
|
72
|
+
${ETQ_CMD} notify webhook github \
|
|
73
|
+
--payload "$GITHUB_EVENT_PATH" \
|
|
74
|
+
--delivery "$GITHUB_RUN_ID" \
|
|
75
|
+
--event push \
|
|
76
|
+
--signature-256 "$SIGNATURE" \
|
|
77
|
+
--json > w1-wake.json
|
|
78
|
+
|
|
79
|
+
node <<'NODE'
|
|
80
|
+
const fs = require('node:fs');
|
|
81
|
+
const wake = JSON.parse(fs.readFileSync('w1-wake.json', 'utf8'));
|
|
82
|
+
const sha = process.env.GITHUB_SHA;
|
|
83
|
+
if (wake.schema !== 'github-webhook-wake.v0') throw new Error('unexpected wake schema');
|
|
84
|
+
if (wake.after !== sha) throw new Error(`wake.after ${wake.after} did not match GITHUB_SHA ${sha}`);
|
|
85
|
+
if (wake.transport.webhook_payload_trusted_as_truth !== false) throw new Error('payload truth boundary drifted');
|
|
86
|
+
if (wake.transport.receiver_must_fetch_and_verify !== true) throw new Error('fetch/verify boundary missing');
|
|
87
|
+
if (wake.payload_boundary.pointers_only !== true) throw new Error('pointer-only boundary drifted');
|
|
88
|
+
if (wake.authority_boundary.notification_is_authority !== false) throw new Error('authority boundary drifted');
|
|
89
|
+
NODE
|
|
90
|
+
|
|
91
|
+
git fetch --no-tags --depth=1 origin "$GITHUB_SHA"
|
|
92
|
+
test "$(git rev-parse FETCH_HEAD)" = "$GITHUB_SHA"
|
|
93
|
+
git cat-file -e "$GITHUB_SHA^{commit}"
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
echo "### Etiquette W1 wake canary"
|
|
97
|
+
echo ""
|
|
98
|
+
echo "- schema: github-webhook-wake.v0"
|
|
99
|
+
echo "- repository: $(node -e "const w=require('./w1-wake.json'); console.log(w.repository.full_name)")"
|
|
100
|
+
echo "- ref: $(node -e "const w=require('./w1-wake.json'); console.log(w.ref)")"
|
|
101
|
+
echo "- after: $(node -e "const w=require('./w1-wake.json'); console.log(w.after)")"
|
|
102
|
+
echo "- signature_verified: true"
|
|
103
|
+
echo "- notification_is_authority: false"
|
|
104
|
+
echo "- next_action: receiver_fetch_and_verify_git_ref"
|
|
105
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
106
|
+
|
|
107
|
+
- name: Upload pointer-only wake artifact
|
|
108
|
+
uses: actions/upload-artifact@v4
|
|
109
|
+
with:
|
|
110
|
+
name: etiquette-w1-wake
|
|
111
|
+
path: w1-wake.json
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Hosted Receiver Templates
|
|
2
|
+
|
|
3
|
+
Opt-in templates for teams that want to expose Etiquette's W1 GitHub webhook
|
|
4
|
+
receiver behind their own hosting layer.
|
|
5
|
+
|
|
6
|
+
## W1 GitHub Receiver
|
|
7
|
+
|
|
8
|
+
`w1-github-webhook-receiver.mjs` is a small Node HTTP server:
|
|
9
|
+
|
|
10
|
+
- `GET /health` returns a readiness probe.
|
|
11
|
+
- `POST /github` accepts GitHub `ping` and `push` webhook deliveries.
|
|
12
|
+
- `ping` returns `200` only after the delivery is signature-verified.
|
|
13
|
+
- `push` delegates to `etiquette notify webhook github`.
|
|
14
|
+
- raw payloads are written only to a temporary file for the CLI call, then
|
|
15
|
+
removed.
|
|
16
|
+
- emitted records stay pointer-only and authority-false.
|
|
17
|
+
|
|
18
|
+
Run it with:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
export ETIQUETTE_GITHUB_WEBHOOK_SECRET='<webhook-secret>'
|
|
22
|
+
export ETIQUETTE_CLI='etiquette'
|
|
23
|
+
node w1-github-webhook-receiver.mjs
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Optional:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
export PORT=8080
|
|
30
|
+
export ETIQUETTE_WAKE_OUT_DIR=/var/lib/etiquette/wake
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`ETIQUETTE_WAKE_OUT_DIR` stores only the verified pointer output, never the raw
|
|
34
|
+
webhook body.
|
|
35
|
+
|
|
36
|
+
## Boundary
|
|
37
|
+
|
|
38
|
+
This template does not create a public endpoint, register repository webhooks,
|
|
39
|
+
provision cloud infrastructure, start remote execution, or grant authority. Host
|
|
40
|
+
selection, webhook registration, secret rotation, and retention policy remain
|
|
41
|
+
operator decisions.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const port = Number(process.env.PORT || '8080');
|
|
10
|
+
const etiquetteCli = process.env.ETIQUETTE_CLI || 'etiquette';
|
|
11
|
+
const secret = process.env.ETIQUETTE_GITHUB_WEBHOOK_SECRET;
|
|
12
|
+
const outDir = process.env.ETIQUETTE_WAKE_OUT_DIR || '';
|
|
13
|
+
|
|
14
|
+
if (!secret) throw new Error('ETIQUETTE_GITHUB_WEBHOOK_SECRET is required');
|
|
15
|
+
|
|
16
|
+
function readRaw(req) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
20
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
21
|
+
req.on('error', reject);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeJson(res, status, value) {
|
|
26
|
+
res.writeHead(status, {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
'cache-control': 'no-store',
|
|
29
|
+
});
|
|
30
|
+
res.end(`${JSON.stringify(value, null, 2)}\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function publicRecord(record) {
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
schema: record.schema,
|
|
37
|
+
event: record.event,
|
|
38
|
+
delivery_id: record.delivery_id,
|
|
39
|
+
repository: record.repository.full_name,
|
|
40
|
+
ref: record.ref || null,
|
|
41
|
+
after: record.after || null,
|
|
42
|
+
signature_verified: record.transport.signature_verified,
|
|
43
|
+
pointers_only: record.payload_boundary.pointers_only,
|
|
44
|
+
notification_is_authority: record.authority_boundary.notification_is_authority,
|
|
45
|
+
next_action: record.next_action,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function verifyDelivery(req, event, delivery, signature) {
|
|
50
|
+
const raw = await readRaw(req);
|
|
51
|
+
const payloadPath = join(tmpdir(), `etiquette-webhook-${delivery}-${randomUUID()}.json`);
|
|
52
|
+
writeFileSync(payloadPath, raw);
|
|
53
|
+
try {
|
|
54
|
+
const result = spawnSync(etiquetteCli, [
|
|
55
|
+
'notify',
|
|
56
|
+
'webhook',
|
|
57
|
+
'github',
|
|
58
|
+
'--payload',
|
|
59
|
+
payloadPath,
|
|
60
|
+
'--delivery',
|
|
61
|
+
delivery,
|
|
62
|
+
'--event',
|
|
63
|
+
event,
|
|
64
|
+
'--signature-256',
|
|
65
|
+
signature,
|
|
66
|
+
'--json',
|
|
67
|
+
], {
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
env: {
|
|
70
|
+
...process.env,
|
|
71
|
+
ETIQUETTE_GITHUB_WEBHOOK_SECRET: secret,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (result.status !== 0) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
status: 401,
|
|
78
|
+
body: {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: 'signature_or_payload_rejected',
|
|
81
|
+
detail: result.stderr.trim().slice(0, 200),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const record = JSON.parse(result.stdout);
|
|
86
|
+
if (outDir) {
|
|
87
|
+
mkdirSync(outDir, { recursive: true });
|
|
88
|
+
writeFileSync(join(outDir, 'latest.json'), `${JSON.stringify(record, null, 2)}\n`);
|
|
89
|
+
writeFileSync(join(outDir, `${delivery}.json`), `${JSON.stringify(record, null, 2)}\n`);
|
|
90
|
+
}
|
|
91
|
+
return { ok: true, status: 200, body: publicRecord(record) };
|
|
92
|
+
} finally {
|
|
93
|
+
rmSync(payloadPath, { force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const server = createServer(async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
100
|
+
writeJson(res, 200, { ok: true, authority: false });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (req.method !== 'POST' || req.url !== '/github') {
|
|
104
|
+
writeJson(res, 404, { ok: false, error: 'not_found' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const event = String(req.headers['x-github-event'] || '');
|
|
109
|
+
const delivery = String(req.headers['x-github-delivery'] || randomUUID());
|
|
110
|
+
const signature = String(req.headers['x-hub-signature-256'] || '');
|
|
111
|
+
if (event !== 'push' && event !== 'ping') {
|
|
112
|
+
writeJson(res, 400, { ok: false, error: 'unsupported_event', event });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!signature.startsWith('sha256=')) {
|
|
116
|
+
writeJson(res, 401, { ok: false, error: 'missing_signature' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = await verifyDelivery(req, event, delivery, signature);
|
|
121
|
+
writeJson(res, result.status, result.body);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
writeJson(res, 500, { ok: false, error: String(error?.message || error) });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
server.listen(port, '0.0.0.0', () => {
|
|
128
|
+
console.log(`etiquette_w1_receiver: listening on ${port}`);
|
|
129
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Seat Packs V0
|
|
2
|
+
|
|
3
|
+
Optional runtime-specific seat templates for Etiquette.
|
|
4
|
+
|
|
5
|
+
Vanilla Etiquette starts with one generic `control-seat`. Use these templates only after the control seat decides a runtime-specific seat is useful.
|
|
6
|
+
|
|
7
|
+
## Available Seat Templates
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
codex:
|
|
11
|
+
execution_surface: codex
|
|
12
|
+
template: source/codex-seat
|
|
13
|
+
|
|
14
|
+
claude-code:
|
|
15
|
+
execution_surface: claude_code
|
|
16
|
+
template: source/claude-code-seat
|
|
17
|
+
|
|
18
|
+
gemini:
|
|
19
|
+
execution_surface: gemini_cli
|
|
20
|
+
template: source/gemini-seat
|
|
21
|
+
|
|
22
|
+
openrouter:
|
|
23
|
+
execution_surface: openrouter
|
|
24
|
+
template: source/openrouter-seat
|
|
25
|
+
|
|
26
|
+
ollama:
|
|
27
|
+
execution_surface: ollama
|
|
28
|
+
template: source/ollama-seat
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Provision Explicitly
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
sh bin/provision-seat codex /tmp/dev-codex-seat
|
|
35
|
+
sh bin/provision-seat claude-code /tmp/dev-claude-code-seat
|
|
36
|
+
sh bin/provision-seat gemini /tmp/dev-gemini-seat
|
|
37
|
+
sh bin/provision-seat openrouter /tmp/dev-openrouter-seat
|
|
38
|
+
sh bin/provision-seat ollama /tmp/dev-ollama-seat
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Seat packs do not bundle monitor skills. For bounded wake checks, every seat
|
|
42
|
+
uses the portable core command `etiquette notify poll --seat <id> --after-seq <n>`,
|
|
43
|
+
which is read-only, does not grant authority, and does not start a daemon.
|
|
44
|
+
|
|
45
|
+
## Provision By Detection
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
sh bin/provision-seat auto /tmp/dev-seat
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Auto-detect succeeds only when exactly one supported runtime is obvious. If several runtimes are available, choose explicitly.
|
|
52
|
+
|
|
53
|
+
## Head-Start Discipline
|
|
54
|
+
|
|
55
|
+
Runtime seat packs are not authority. After provisioning a seat into a project,
|
|
56
|
+
the seat inherits the target project's ledger and task envelopes. Read the
|
|
57
|
+
project head-start surfaces before pickup:
|
|
58
|
+
|
|
59
|
+
- `AGENTS.md`
|
|
60
|
+
- `docs/work/BOARD.md`
|
|
61
|
+
- `docs/work/runbooks/SESSION_PICKUP.md`
|
|
62
|
+
- `docs/work/manuals/SEAT_OPERATING_MANUAL.md`
|
|
63
|
+
- `docs/work/cookbooks/ADD_A_LANE.md` when routing work
|
|
64
|
+
|
|
65
|
+
Use `$etiquette-seat-lifecycle` before pickup after stale context, resume,
|
|
66
|
+
runner/model/profile swaps, or workflow migration.
|
|
67
|
+
|
|
68
|
+
## Validate Catalog
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
sh validate-seat-packs.sh
|
|
72
|
+
```
|