@etiquekit/etq 0.0.1 → 1.0.1
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 +94 -0
- package/README.md +77 -9
- package/bin/etiquette +1149 -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 +68 -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,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/post-tool-use — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook post_tool_use`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook post_tool_use --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook post_tool_use --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/session-end — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook session_end`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook session_end --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook session_end --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/session-start — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook session_start`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook session_start --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook session_start --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/stop — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook stop`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook stop --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook stop --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/user-prompt-submit — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook user_prompt_submit`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook user_prompt_submit --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook user_prompt_submit --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
if [ "$#" -lt 4 ]; then
|
|
7
|
+
echo "usage: sh bin/local-event <task-id> <actor> <action> <message...>" >&2
|
|
8
|
+
exit 2
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
task_id=$1
|
|
12
|
+
actor=$2
|
|
13
|
+
action=$3
|
|
14
|
+
shift 3
|
|
15
|
+
message=$*
|
|
16
|
+
|
|
17
|
+
event_log=${ETIQUETTE_EVENT_LOG:-"$ROOT/.etiquette/events/local.ndjson"}
|
|
18
|
+
event_dir=$(dirname -- "$event_log")
|
|
19
|
+
mkdir -p "$event_dir"
|
|
20
|
+
|
|
21
|
+
lock_dir="$event_dir/.local-event.lock"
|
|
22
|
+
lock_attempts=0
|
|
23
|
+
while ! mkdir "$lock_dir" 2>/dev/null; do
|
|
24
|
+
lock_attempts=$((lock_attempts + 1))
|
|
25
|
+
if [ "$lock_attempts" -ge 100 ]; then
|
|
26
|
+
echo "timed out waiting for local event lock: $lock_dir" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
sleep 0.05
|
|
30
|
+
done
|
|
31
|
+
trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM
|
|
32
|
+
|
|
33
|
+
if [ -f "$event_log" ]; then
|
|
34
|
+
seq=$(wc -l < "$event_log" | tr -d ' ')
|
|
35
|
+
else
|
|
36
|
+
seq=0
|
|
37
|
+
fi
|
|
38
|
+
seq=$((seq + 1))
|
|
39
|
+
at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
40
|
+
|
|
41
|
+
json_escape() {
|
|
42
|
+
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
task_id_json=$(json_escape "$task_id")
|
|
46
|
+
actor_json=$(json_escape "$actor")
|
|
47
|
+
action_json=$(json_escape "$action")
|
|
48
|
+
message_json=$(json_escape "$message")
|
|
49
|
+
|
|
50
|
+
printf '{"seq":%s,"at":"%s","task_id":"%s","actor":"%s","action":"%s","message":"%s"}\n' \
|
|
51
|
+
"$seq" "$at" "$task_id_json" "$actor_json" "$action_json" "$message_json" >> "$event_log"
|
|
52
|
+
|
|
53
|
+
printf 'event: %s seq:%s\n' "$event_log" "$seq"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# post-receipt: push a receipt METADATA pointer to the hosted plane.
|
|
3
|
+
#
|
|
4
|
+
# Per hosted-plane-boundary.v0 — content stays in the repo; only the
|
|
5
|
+
# pointer (task_id / seat_id / status / artifact_ref / receipt_ref /
|
|
6
|
+
# validation_summary capped) is sent. Sanitisers reject path/hostname/IP
|
|
7
|
+
# leakage in the refs.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# sh bin/post-receipt --task-id DEMO-001 --status success \
|
|
11
|
+
# --artifact-ref repo:main --receipt-ref docs/work/receipts/DEMO-001.md \
|
|
12
|
+
# --validation-summary "product-doctor: ok"
|
|
13
|
+
set -eu
|
|
14
|
+
|
|
15
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
16
|
+
# shellcheck source=_lib/sanitize.sh
|
|
17
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
18
|
+
# shellcheck source=_lib/state.sh
|
|
19
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
20
|
+
# shellcheck source=_lib/http.sh
|
|
21
|
+
. "$ROOT/bin/_lib/http.sh"
|
|
22
|
+
|
|
23
|
+
ALLOWED_STATUSES="success partial blocked failed"
|
|
24
|
+
|
|
25
|
+
task_id=""
|
|
26
|
+
status=""
|
|
27
|
+
artifact_ref=""
|
|
28
|
+
receipt_ref=""
|
|
29
|
+
validation_summary=""
|
|
30
|
+
|
|
31
|
+
while [ "$#" -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--task-id) task_id=$2; shift 2 ;;
|
|
34
|
+
--status) status=$2; shift 2 ;;
|
|
35
|
+
--artifact-ref) artifact_ref=$2; shift 2 ;;
|
|
36
|
+
--receipt-ref) receipt_ref=$2; shift 2 ;;
|
|
37
|
+
--validation-summary) validation_summary=$2; shift 2 ;;
|
|
38
|
+
-h|--help)
|
|
39
|
+
cat <<EOF
|
|
40
|
+
post-receipt — push a receipt metadata pointer
|
|
41
|
+
|
|
42
|
+
sh bin/post-receipt --task-id <id> --status <s> \\
|
|
43
|
+
[--artifact-ref <ref>] [--receipt-ref <repo-relative-path>] \\
|
|
44
|
+
[--validation-summary <≤200 chars>]
|
|
45
|
+
|
|
46
|
+
Status (4):
|
|
47
|
+
$(printf ' - %s\n' $ALLOWED_STATUSES)
|
|
48
|
+
|
|
49
|
+
Requires seat-consume-pass to have populated workspace.json.
|
|
50
|
+
Only metadata flows over the wire; receipt body stays in your repo.
|
|
51
|
+
EOF
|
|
52
|
+
exit 0
|
|
53
|
+
;;
|
|
54
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
[ -n "$task_id" ] || { echo "post-receipt: --task-id required" >&2; exit 2; }
|
|
59
|
+
[ -n "$status" ] || { echo "post-receipt: --status required" >&2; exit 2; }
|
|
60
|
+
|
|
61
|
+
# Status allowlist.
|
|
62
|
+
ok=0
|
|
63
|
+
for s in $ALLOWED_STATUSES; do
|
|
64
|
+
if [ "$s" = "$status" ]; then ok=1; break; fi
|
|
65
|
+
done
|
|
66
|
+
if [ "$ok" -ne 1 ]; then
|
|
67
|
+
printf 'post-receipt: --status "%s" not in allowlist:\n' "$status" >&2
|
|
68
|
+
printf ' - %s\n' $ALLOWED_STATUSES >&2
|
|
69
|
+
exit 2
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# task_id length cap (defence; api caps at 128).
|
|
73
|
+
if [ "${#task_id}" -gt 128 ]; then
|
|
74
|
+
echo "post-receipt: --task-id length > 128" >&2
|
|
75
|
+
exit 2
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Sanitise optional refs.
|
|
79
|
+
clean_artifact=""
|
|
80
|
+
if [ -n "$artifact_ref" ]; then
|
|
81
|
+
if ! clean_artifact=$(etq_sanitize_artifact_ref "$artifact_ref"); then
|
|
82
|
+
echo "post-receipt: --artifact-ref rejected" >&2
|
|
83
|
+
exit 2
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
clean_receipt=""
|
|
87
|
+
if [ -n "$receipt_ref" ]; then
|
|
88
|
+
if ! clean_receipt=$(etq_sanitize_repo_relative "$receipt_ref"); then
|
|
89
|
+
echo "post-receipt: --receipt-ref rejected" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
# Validation summary cap at 200 chars (server also enforces).
|
|
94
|
+
if [ "${#validation_summary}" -gt 200 ]; then
|
|
95
|
+
validation_summary=$(printf '%s' "$validation_summary" | cut -c1-200)
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Load workspace.json.
|
|
99
|
+
workspace_file=$(etq_state_workspace_path)
|
|
100
|
+
if [ ! -f "$workspace_file" ]; then
|
|
101
|
+
echo "post-receipt: workspace.json missing — run \`sh bin/seat-consume-pass --pass-id <id>\` first" >&2
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
workspace_id=$(etq_state_read_key "$workspace_file" workspace_id) || exit 1
|
|
105
|
+
pass_id=$(etq_state_read_key "$workspace_file" pass_id) || exit 1
|
|
106
|
+
host=$(etq_state_read_key "$workspace_file" hosted_host) || exit 1
|
|
107
|
+
seat_id=$(etq_state_read_key "$(etq_state_seat_path)" seat_id) || exit 1
|
|
108
|
+
seat_id=$(etq_sanitize_opaque_id "$seat_id") || {
|
|
109
|
+
echo "post-receipt: seat.json contains invalid seat_id; re-run seat-init --force" >&2
|
|
110
|
+
exit 1
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Build body with jq for safe JSON construction.
|
|
114
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
115
|
+
echo "post-receipt: jq required" >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
body=$(jq -nc \
|
|
119
|
+
--arg pass_id "$pass_id" \
|
|
120
|
+
--arg task_id "$task_id" \
|
|
121
|
+
--arg seat_id "$seat_id" \
|
|
122
|
+
--arg status "$status" \
|
|
123
|
+
--arg artifact_ref "$clean_artifact" \
|
|
124
|
+
--arg receipt_ref "$clean_receipt" \
|
|
125
|
+
--arg validation_summary "$validation_summary" \
|
|
126
|
+
'
|
|
127
|
+
{pass_id: $pass_id, task_id: $task_id, seat_id: $seat_id, status: $status}
|
|
128
|
+
+ (if $artifact_ref != "" then {artifact_ref: $artifact_ref} else {} end)
|
|
129
|
+
+ (if $receipt_ref != "" then {receipt_ref: $receipt_ref} else {} end)
|
|
130
|
+
+ (if $validation_summary != "" then {validation_summary: $validation_summary} else {} end)
|
|
131
|
+
')
|
|
132
|
+
|
|
133
|
+
url="$host/workspaces/$workspace_id/receipts"
|
|
134
|
+
printf 'post-receipt: POST %s task=%s status=%s\n' "$url" "$task_id" "$status"
|
|
135
|
+
|
|
136
|
+
response=$(etq_http_post "$url" "$body") || {
|
|
137
|
+
rc=$?
|
|
138
|
+
echo "post-receipt: ingest failed (http rc=$rc)" >&2
|
|
139
|
+
exit "$rc"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
printf 'post-receipt: ingested\n'
|
|
143
|
+
printf '%s\n' "$response" | jq -c . 2>/dev/null || printf '%s\n' "$response"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# post-telemetry: push a daily-grain telemetry event to the hosted plane.
|
|
3
|
+
#
|
|
4
|
+
# Per telemetry.v0 — event_type must be in the 8-event allowlist; meta is
|
|
5
|
+
# bounded (keys ≤32 chars, string values ≤64 chars and reject path/hostname
|
|
6
|
+
# patterns) before anything is sent over the wire.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# sh bin/post-telemetry --event-type seat_doctor_run
|
|
10
|
+
# sh bin/post-telemetry --event-type seat_doctor_run --count 1 --meta '{"p50_ms":140}'
|
|
11
|
+
set -eu
|
|
12
|
+
|
|
13
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
14
|
+
# shellcheck source=_lib/sanitize.sh
|
|
15
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
16
|
+
# shellcheck source=_lib/state.sh
|
|
17
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
18
|
+
# shellcheck source=_lib/http.sh
|
|
19
|
+
. "$ROOT/bin/_lib/http.sh"
|
|
20
|
+
|
|
21
|
+
ALLOWED_EVENTS="pass_issued pass_revoked pass_consumed seat_readiness_posted seat_verified receipt_submitted seat_doctor_run validate_vanilla_run"
|
|
22
|
+
|
|
23
|
+
event_type=""
|
|
24
|
+
count=1
|
|
25
|
+
meta='{}'
|
|
26
|
+
|
|
27
|
+
while [ "$#" -gt 0 ]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--event-type) event_type=$2; shift 2 ;;
|
|
30
|
+
--count) count=$2; shift 2 ;;
|
|
31
|
+
--meta) meta=$2; shift 2 ;;
|
|
32
|
+
-h|--help)
|
|
33
|
+
cat <<EOF
|
|
34
|
+
post-telemetry — push a daily-grain telemetry event
|
|
35
|
+
|
|
36
|
+
sh bin/post-telemetry --event-type <type> [--count N] [--meta JSON]
|
|
37
|
+
|
|
38
|
+
Allowed event_type values (8):
|
|
39
|
+
$(printf ' - %s\n' $ALLOWED_EVENTS)
|
|
40
|
+
|
|
41
|
+
Requires seat-consume-pass to have populated workspace.json.
|
|
42
|
+
EOF
|
|
43
|
+
exit 0
|
|
44
|
+
;;
|
|
45
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
if [ -z "$event_type" ]; then
|
|
50
|
+
echo "post-telemetry: --event-type is required" >&2
|
|
51
|
+
exit 2
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Allowlist check client-side (saves a round trip).
|
|
55
|
+
allowed=0
|
|
56
|
+
for e in $ALLOWED_EVENTS; do
|
|
57
|
+
if [ "$e" = "$event_type" ]; then allowed=1; break; fi
|
|
58
|
+
done
|
|
59
|
+
if [ "$allowed" -ne 1 ]; then
|
|
60
|
+
printf 'post-telemetry: event_type "%s" not in allowlist:\n' "$event_type" >&2
|
|
61
|
+
printf ' - %s\n' $ALLOWED_EVENTS >&2
|
|
62
|
+
exit 2
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# count must be a positive integer.
|
|
66
|
+
case "$count" in
|
|
67
|
+
*[!0-9]*) echo "post-telemetry: --count must be a positive integer" >&2; exit 2 ;;
|
|
68
|
+
'') echo "post-telemetry: --count empty" >&2; exit 2 ;;
|
|
69
|
+
esac
|
|
70
|
+
|
|
71
|
+
# Meta must be a JSON object.
|
|
72
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
73
|
+
echo "post-telemetry: jq required" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
if ! printf '%s' "$meta" | jq -e 'type == "object"' >/dev/null 2>&1; then
|
|
77
|
+
echo "post-telemetry: --meta must be a JSON object" >&2
|
|
78
|
+
exit 2
|
|
79
|
+
fi
|
|
80
|
+
if ! clean_meta=$(etq_sanitize_meta_json "$meta"); then
|
|
81
|
+
echo "post-telemetry: --meta sanitization failed" >&2
|
|
82
|
+
exit 2
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Load workspace.json.
|
|
86
|
+
workspace_file=$(etq_state_workspace_path)
|
|
87
|
+
if [ ! -f "$workspace_file" ]; then
|
|
88
|
+
echo "post-telemetry: workspace.json missing — run \`sh bin/seat-consume-pass --pass-id <id>\` first" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
workspace_id=$(etq_state_read_key "$workspace_file" workspace_id) || exit 1
|
|
92
|
+
pass_id=$(etq_state_read_key "$workspace_file" pass_id) || exit 1
|
|
93
|
+
host=$(etq_state_read_key "$workspace_file" hosted_host) || exit 1
|
|
94
|
+
|
|
95
|
+
body=$(jq -nc \
|
|
96
|
+
--arg pass_id "$pass_id" \
|
|
97
|
+
--arg event_type "$event_type" \
|
|
98
|
+
--argjson count "$count" \
|
|
99
|
+
--argjson meta "$clean_meta" \
|
|
100
|
+
'{pass_id: $pass_id, event_type: $event_type, count: $count, meta: $meta}')
|
|
101
|
+
|
|
102
|
+
url="$host/workspaces/$workspace_id/telemetry"
|
|
103
|
+
printf 'post-telemetry: POST %s event_type=%s count=%s\n' "$url" "$event_type" "$count"
|
|
104
|
+
|
|
105
|
+
response=$(etq_http_post "$url" "$body") || {
|
|
106
|
+
rc=$?
|
|
107
|
+
echo "post-telemetry: ingest failed (http rc=$rc)" >&2
|
|
108
|
+
exit "$rc"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
printf 'post-telemetry: ingested\n'
|
|
112
|
+
printf '%s\n' "$response" | jq -c . 2>/dev/null || printf '%s\n' "$response"
|