@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,115 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# seat-consume-pass: redeem a setup-pass against the hosted plane and cache
|
|
3
|
+
# the returned envelope into $STATE_DIR/workspace.json.
|
|
4
|
+
#
|
|
5
|
+
# Per pass.v0 contract. No credentials are returned; only routing fields
|
|
6
|
+
# (workspace_id, org_id, seat_template, requested_runtime, artifact_type,
|
|
7
|
+
# allowed_repo_binding).
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# sh bin/seat-consume-pass --pass-id <pass-id>
|
|
11
|
+
# sh bin/seat-consume-pass --pass-id <pass-id> --host http://localhost:4000
|
|
12
|
+
set -eu
|
|
13
|
+
|
|
14
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
15
|
+
# shellcheck source=_lib/sanitize.sh
|
|
16
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
17
|
+
# shellcheck source=_lib/state.sh
|
|
18
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
19
|
+
# shellcheck source=_lib/http.sh
|
|
20
|
+
. "$ROOT/bin/_lib/http.sh"
|
|
21
|
+
|
|
22
|
+
host=${ETIQUETTE_HOSTED_HOST:-http://localhost:4000}
|
|
23
|
+
pass_id=""
|
|
24
|
+
|
|
25
|
+
while [ "$#" -gt 0 ]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--pass-id) pass_id=$2; shift 2 ;;
|
|
28
|
+
--host) host=$2; shift 2 ;;
|
|
29
|
+
-h|--help)
|
|
30
|
+
cat <<'EOF'
|
|
31
|
+
seat-consume-pass — redeem a setup-pass; cache envelope to workspace.json
|
|
32
|
+
|
|
33
|
+
sh bin/seat-consume-pass --pass-id <pass-id> [--host <url>]
|
|
34
|
+
|
|
35
|
+
Requires seat-init to have run first (needs seat.json for consumer_seat_id).
|
|
36
|
+
Stores envelope at $STATE_DIR/workspace.json on success.
|
|
37
|
+
EOF
|
|
38
|
+
exit 0
|
|
39
|
+
;;
|
|
40
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
if [ -z "$pass_id" ]; then
|
|
45
|
+
echo "seat-consume-pass: --pass-id is required" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Pass-id format check: it's a ULID we received from the dashboard; let the
|
|
50
|
+
# server be the authority on validity, but reject obvious junk (whitespace,
|
|
51
|
+
# excessive length).
|
|
52
|
+
case "$pass_id" in
|
|
53
|
+
*' '*|*"$(printf '\t')"*) echo "seat-consume-pass: pass-id has whitespace" >&2; exit 2 ;;
|
|
54
|
+
esac
|
|
55
|
+
if [ "${#pass_id}" -gt 64 ]; then
|
|
56
|
+
echo "seat-consume-pass: pass-id length > 64; suspicious" >&2
|
|
57
|
+
exit 2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
seat_file=$(etq_state_seat_path)
|
|
61
|
+
if [ ! -f "$seat_file" ]; then
|
|
62
|
+
cat >&2 <<EOF
|
|
63
|
+
seat-consume-pass: seat.json missing — run \`sh bin/seat-init\` first.
|
|
64
|
+
EOF
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
seat_id=$(etq_state_read_key "$seat_file" seat_id) || exit 1
|
|
69
|
+
# Defence: confirm seat_id still sanitiser-clean (in case of manual edits).
|
|
70
|
+
if ! seat_id=$(etq_sanitize_opaque_id "$seat_id"); then
|
|
71
|
+
echo "seat-consume-pass: seat.json contains invalid seat_id; re-run seat-init --force" >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Build request body.
|
|
76
|
+
body=$(printf '{"consumer_seat_id":"%s"}' "$seat_id")
|
|
77
|
+
|
|
78
|
+
url="$host/passes/$pass_id/consume"
|
|
79
|
+
printf 'seat-consume-pass: POST %s\n' "$url"
|
|
80
|
+
|
|
81
|
+
response=$(etq_http_post "$url" "$body") || {
|
|
82
|
+
rc=$?
|
|
83
|
+
echo "seat-consume-pass: pass redemption failed (http rc=$rc)" >&2
|
|
84
|
+
exit "$rc"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Sanity-check the envelope.
|
|
88
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
89
|
+
echo "seat-consume-pass: jq required to parse envelope" >&2
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
workspace_id=$(printf '%s' "$response" | jq -r '.workspace_id // empty')
|
|
94
|
+
org_id=$(printf '%s' "$response" | jq -r '.org_id // empty')
|
|
95
|
+
|
|
96
|
+
if [ -z "$workspace_id" ] || [ -z "$org_id" ]; then
|
|
97
|
+
echo "seat-consume-pass: envelope missing workspace_id or org_id" >&2
|
|
98
|
+
printf '%s\n' "$response" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
103
|
+
# Augment envelope with consumption-side metadata (pass_id + host + ts).
|
|
104
|
+
augmented=$(printf '%s' "$response" | jq \
|
|
105
|
+
--arg pass_id "$pass_id" \
|
|
106
|
+
--arg host "$host" \
|
|
107
|
+
--arg consumed_at "$now" \
|
|
108
|
+
'. + {pass_id: $pass_id, hosted_host: $host, cached_at: $consumed_at}')
|
|
109
|
+
|
|
110
|
+
workspace_file=$(etq_state_workspace_path)
|
|
111
|
+
etq_state_write_file "$workspace_file" "$augmented"
|
|
112
|
+
|
|
113
|
+
printf 'seat-consume-pass: cached envelope to %s\n' "$workspace_file"
|
|
114
|
+
printf 'seat-consume-pass: workspace_id=%s org_id=%s\n' "$workspace_id" "$org_id"
|
|
115
|
+
printf 'seat-consume-pass: next: sh bin/seat-post-readiness\n'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
test -f "$ROOT/.llm-substrate/HANDOFF.md"
|
|
7
|
+
test -f "$ROOT/docs/work/ledger/LEDGER.md"
|
|
8
|
+
test -f "$ROOT/docs/work/access/workspace-secure-profile.v0.json"
|
|
9
|
+
test -f "$ROOT/docs/work/access/ACCESS-ASSURANCE.md"
|
|
10
|
+
test -f "$ROOT/docs/work/playbacks/PLAYBACK_TEMPLATE.md"
|
|
11
|
+
test -f "$ROOT/docs/work/tasks/CONTROL-001.yaml"
|
|
12
|
+
test -d "$ROOT/docs/work/receipts"
|
|
13
|
+
test -f "$ROOT/bin/access-assurance-check"
|
|
14
|
+
test -f "$ROOT/bin/session-init"
|
|
15
|
+
test -f "$ROOT/bin/local-event"
|
|
16
|
+
test -f "$ROOT/bin/compact-local-events"
|
|
17
|
+
|
|
18
|
+
sh "$ROOT/bin/access-assurance-check" >/dev/null
|
|
19
|
+
|
|
20
|
+
echo "control-seat: ok"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# seat-init: mint an opaque seat_id and write $STATE_DIR/seat.json.
|
|
3
|
+
#
|
|
4
|
+
# Opaque seat_id only: no hostnames, paths, IPs, unsafe characters, or
|
|
5
|
+
# values over 64 chars. Idempotent: re-runs preserve existing seat_id
|
|
6
|
+
# unless --force is passed.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# sh bin/seat-init # mint if missing
|
|
10
|
+
# sh bin/seat-init --execution-seat cc-seat # set seat label
|
|
11
|
+
# sh bin/seat-init --force # re-mint even if seat.json exists
|
|
12
|
+
set -eu
|
|
13
|
+
|
|
14
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
15
|
+
# shellcheck source=_lib/sanitize.sh
|
|
16
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
17
|
+
# shellcheck source=_lib/state.sh
|
|
18
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
19
|
+
|
|
20
|
+
execution_seat=${ETIQUETTE_EXECUTION_SEAT:-control-seat}
|
|
21
|
+
force=0
|
|
22
|
+
while [ "$#" -gt 0 ]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--execution-seat) execution_seat=$2; shift 2 ;;
|
|
25
|
+
--force) force=1; shift ;;
|
|
26
|
+
-h|--help)
|
|
27
|
+
cat <<'EOF'
|
|
28
|
+
seat-init — mint opaque seat_id; bootstrap $STATE_DIR/seat.json
|
|
29
|
+
|
|
30
|
+
sh bin/seat-init [--execution-seat <label>] [--force]
|
|
31
|
+
|
|
32
|
+
Writes ~/.etiquette/state/seat.json under the seat root with:
|
|
33
|
+
seat_id opaque ULID-style id (≤64 chars; no hostnames/paths/IPs)
|
|
34
|
+
execution_seat caller-supplied label (default control-seat)
|
|
35
|
+
created_at iso8601 timestamp
|
|
36
|
+
|
|
37
|
+
Idempotent: re-runs preserve seat_id unless --force.
|
|
38
|
+
EOF
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
# Validate execution_seat is sanitiser-clean (it ends up in payloads).
|
|
46
|
+
if ! execution_seat=$(etq_sanitize_opaque_id "$execution_seat"); then
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
etq_state_init_dir
|
|
51
|
+
seat_file=$(etq_state_seat_path)
|
|
52
|
+
|
|
53
|
+
if [ -f "$seat_file" ] && [ "$force" -eq 0 ]; then
|
|
54
|
+
existing_id=$(etq_state_read_key "$seat_file" seat_id 2>/dev/null || true)
|
|
55
|
+
if [ -n "$existing_id" ]; then
|
|
56
|
+
printf 'seat-init: seat.json already exists (seat_id=%s); use --force to re-mint\n' "$existing_id"
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Generate an opaque seat_id. Shape: seat-<unixtime>-<8 hex>
|
|
62
|
+
# This is opaque (no hostname/path/IP), bounded length, sanitiser-clean.
|
|
63
|
+
ts=$(date +%s)
|
|
64
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
65
|
+
rand=$(openssl rand -hex 4)
|
|
66
|
+
elif [ -r /dev/urandom ]; then
|
|
67
|
+
rand=$(od -An -N4 -tx1 /dev/urandom | tr -d ' \n')
|
|
68
|
+
else
|
|
69
|
+
# Last-resort PRNG; not cryptographic, but seat_id is not a secret.
|
|
70
|
+
rand=$(awk 'BEGIN{srand(); printf "%08x", int(rand()*4294967295)}')
|
|
71
|
+
fi
|
|
72
|
+
seat_id="seat-${ts}-${rand}"
|
|
73
|
+
|
|
74
|
+
# Verify our own output is sanitiser-clean (defence-in-depth).
|
|
75
|
+
if ! seat_id=$(etq_sanitize_opaque_id "$seat_id"); then
|
|
76
|
+
echo "seat-init: generated seat_id failed self-sanitiser; aborting" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
81
|
+
body=$(printf '{"seat_id":"%s","execution_seat":"%s","created_at":"%s"}' \
|
|
82
|
+
"$seat_id" "$execution_seat" "$now")
|
|
83
|
+
|
|
84
|
+
etq_state_write_file "$seat_file" "$body"
|
|
85
|
+
printf 'seat-init: minted seat_id=%s execution_seat=%s\n' "$seat_id" "$execution_seat"
|
|
86
|
+
printf 'seat-init: wrote %s\n' "$seat_file"
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# seat-post-readiness: push capability declaration to the hosted plane.
|
|
3
|
+
#
|
|
4
|
+
# Per hosted-plane-boundary.v0 Sycophant problem fix — capability_source
|
|
5
|
+
# defaults to "seat-doctor" (trusted source). "agent-self-declared" is
|
|
6
|
+
# allowed but flagged low-trust at the dashboard. Operator-config is
|
|
7
|
+
# also trusted.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# sh bin/seat-post-readiness --status operational
|
|
11
|
+
# sh bin/seat-post-readiness --status operational --parallel-support \
|
|
12
|
+
# --strategies "solo,telescope,local_adversarial_tango" \
|
|
13
|
+
# --unsupported "avengers"
|
|
14
|
+
# sh bin/seat-post-readiness --capability-source operator-config --status operational
|
|
15
|
+
set -eu
|
|
16
|
+
|
|
17
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
18
|
+
# shellcheck source=_lib/sanitize.sh
|
|
19
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
20
|
+
# shellcheck source=_lib/state.sh
|
|
21
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
22
|
+
# shellcheck source=_lib/http.sh
|
|
23
|
+
. "$ROOT/bin/_lib/http.sh"
|
|
24
|
+
|
|
25
|
+
ALLOWED_STATUSES="operational degraded blocked"
|
|
26
|
+
ALLOWED_SOURCES="operator-config seat-doctor agent-self-declared"
|
|
27
|
+
|
|
28
|
+
status=""
|
|
29
|
+
capability_source="seat-doctor"
|
|
30
|
+
parallel_support="false"
|
|
31
|
+
strategies=""
|
|
32
|
+
unsupported=""
|
|
33
|
+
environment_node_id=""
|
|
34
|
+
|
|
35
|
+
while [ "$#" -gt 0 ]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--status) status=$2; shift 2 ;;
|
|
38
|
+
--capability-source) capability_source=$2; shift 2 ;;
|
|
39
|
+
--parallel-support) parallel_support="true"; shift ;;
|
|
40
|
+
--strategies) strategies=$2; shift 2 ;;
|
|
41
|
+
--unsupported) unsupported=$2; shift 2 ;;
|
|
42
|
+
--environment-node-id) environment_node_id=$2; shift 2 ;;
|
|
43
|
+
-h|--help)
|
|
44
|
+
cat <<EOF
|
|
45
|
+
seat-post-readiness — push capability declaration to the hosted plane
|
|
46
|
+
|
|
47
|
+
sh bin/seat-post-readiness --status <status> \\
|
|
48
|
+
[--capability-source <source>] [--parallel-support] \\
|
|
49
|
+
[--strategies "a,b,c"] [--unsupported "x,y"] \\
|
|
50
|
+
[--environment-node-id <opaque>]
|
|
51
|
+
|
|
52
|
+
Status (3):
|
|
53
|
+
$(printf ' - %s\n' $ALLOWED_STATUSES)
|
|
54
|
+
|
|
55
|
+
Capability source (3; agent-self-declared flagged low-trust):
|
|
56
|
+
$(printf ' - %s\n' $ALLOWED_SOURCES)
|
|
57
|
+
|
|
58
|
+
Defaults: capability_source=seat-doctor, parallel_support=false.
|
|
59
|
+
Requires seat-consume-pass to have populated workspace.json.
|
|
60
|
+
EOF
|
|
61
|
+
exit 0
|
|
62
|
+
;;
|
|
63
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
64
|
+
esac
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
[ -n "$status" ] || { echo "seat-post-readiness: --status required" >&2; exit 2; }
|
|
68
|
+
|
|
69
|
+
# Allowlist checks.
|
|
70
|
+
ok=0
|
|
71
|
+
for s in $ALLOWED_STATUSES; do [ "$s" = "$status" ] && ok=1; done
|
|
72
|
+
if [ "$ok" -ne 1 ]; then
|
|
73
|
+
printf 'seat-post-readiness: --status "%s" not in allowlist:\n' "$status" >&2
|
|
74
|
+
printf ' - %s\n' $ALLOWED_STATUSES >&2
|
|
75
|
+
exit 2
|
|
76
|
+
fi
|
|
77
|
+
ok=0
|
|
78
|
+
for s in $ALLOWED_SOURCES; do [ "$s" = "$capability_source" ] && ok=1; done
|
|
79
|
+
if [ "$ok" -ne 1 ]; then
|
|
80
|
+
printf 'seat-post-readiness: --capability-source "%s" not in allowlist:\n' "$capability_source" >&2
|
|
81
|
+
printf ' - %s\n' $ALLOWED_SOURCES >&2
|
|
82
|
+
exit 2
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Sanitise environment_node_id if supplied.
|
|
86
|
+
clean_env=""
|
|
87
|
+
if [ -n "$environment_node_id" ]; then
|
|
88
|
+
if ! clean_env=$(etq_sanitize_opaque_id "$environment_node_id"); then
|
|
89
|
+
echo "seat-post-readiness: --environment-node-id rejected" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Load state.
|
|
95
|
+
seat_file=$(etq_state_seat_path)
|
|
96
|
+
workspace_file=$(etq_state_workspace_path)
|
|
97
|
+
if [ ! -f "$seat_file" ]; then
|
|
98
|
+
echo "seat-post-readiness: seat.json missing — run \`sh bin/seat-init\` first" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
if [ ! -f "$workspace_file" ]; then
|
|
102
|
+
echo "seat-post-readiness: workspace.json missing — run \`sh bin/seat-consume-pass --pass-id <id>\` first" >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
seat_id=$(etq_state_read_key "$seat_file" seat_id) || exit 1
|
|
107
|
+
execution_seat=$(etq_state_read_key "$seat_file" execution_seat) || exit 1
|
|
108
|
+
workspace_id=$(etq_state_read_key "$workspace_file" workspace_id) || exit 1
|
|
109
|
+
pass_id=$(etq_state_read_key "$workspace_file" pass_id) || exit 1
|
|
110
|
+
host=$(etq_state_read_key "$workspace_file" hosted_host) || exit 1
|
|
111
|
+
|
|
112
|
+
# Validate seat_id + execution_seat against sanitiser (defence).
|
|
113
|
+
seat_id=$(etq_sanitize_opaque_id "$seat_id") || exit 1
|
|
114
|
+
execution_seat=$(etq_sanitize_opaque_id "$execution_seat") || exit 1
|
|
115
|
+
|
|
116
|
+
# Convert comma-separated strategy lists to JSON arrays.
|
|
117
|
+
strategies_to_json() {
|
|
118
|
+
list=$1
|
|
119
|
+
if [ -z "$list" ]; then
|
|
120
|
+
printf '[]'
|
|
121
|
+
return
|
|
122
|
+
fi
|
|
123
|
+
# Use jq for safe construction.
|
|
124
|
+
printf '%s' "$list" | tr ',' '\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -v '^$' \
|
|
125
|
+
| jq -R . | jq -sc .
|
|
126
|
+
}
|
|
127
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
128
|
+
echo "seat-post-readiness: jq required" >&2
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
strategies_json=$(strategies_to_json "$strategies")
|
|
132
|
+
unsupported_json=$(strategies_to_json "$unsupported")
|
|
133
|
+
|
|
134
|
+
# Build body.
|
|
135
|
+
body=$(jq -nc \
|
|
136
|
+
--arg pass_id "$pass_id" \
|
|
137
|
+
--arg seat_id "$seat_id" \
|
|
138
|
+
--arg execution_seat "$execution_seat" \
|
|
139
|
+
--arg status "$status" \
|
|
140
|
+
--arg capability_source "$capability_source" \
|
|
141
|
+
--argjson parallel_support "$parallel_support" \
|
|
142
|
+
--argjson strategies "$strategies_json" \
|
|
143
|
+
--argjson unsupported "$unsupported_json" \
|
|
144
|
+
--arg environment_node_id "$clean_env" \
|
|
145
|
+
'
|
|
146
|
+
{pass_id: $pass_id, seat_id: $seat_id, execution_seat: $execution_seat,
|
|
147
|
+
status: $status, capability_source: $capability_source,
|
|
148
|
+
parallel_support: $parallel_support,
|
|
149
|
+
supported_strategies: $strategies,
|
|
150
|
+
unsupported_strategies: $unsupported}
|
|
151
|
+
+ (if $environment_node_id != "" then {environment_node_id: $environment_node_id} else {} end)
|
|
152
|
+
')
|
|
153
|
+
|
|
154
|
+
url="$host/workspaces/$workspace_id/seat-readiness"
|
|
155
|
+
printf 'seat-post-readiness: POST %s status=%s capability_source=%s\n' \
|
|
156
|
+
"$url" "$status" "$capability_source"
|
|
157
|
+
|
|
158
|
+
response=$(etq_http_post "$url" "$body") || {
|
|
159
|
+
rc=$?
|
|
160
|
+
echo "seat-post-readiness: ingest failed (http rc=$rc)" >&2
|
|
161
|
+
exit "$rc"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
printf 'seat-post-readiness: ingested\n'
|
|
165
|
+
printf '%s\n' "$response" | jq -c . 2>/dev/null || printf '%s\n' "$response"
|
|
166
|
+
|
|
167
|
+
# Capability-source hint if low-trust.
|
|
168
|
+
if [ "$capability_source" = "agent-self-declared" ]; then
|
|
169
|
+
printf '\nseat-post-readiness: NOTE — capability_source=agent-self-declared is flagged low-trust\n' >&2
|
|
170
|
+
printf 'seat-post-readiness: in the dashboard per capability-trust doctrine.\n' >&2
|
|
171
|
+
fi
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# session-init: initialize the live local workflow surface for this seat.
|
|
3
|
+
#
|
|
4
|
+
# This is intentionally local-first. It does not register the seat remotely,
|
|
5
|
+
# consume a pass, start a runtime, clone product repos, or call hosted APIs.
|
|
6
|
+
# It verifies the seat identity, prepares ledger/receipt/task directories,
|
|
7
|
+
# writes session.json, appends one compact ledger line, and emits one local
|
|
8
|
+
# event so a running agent session has a durable local trail from turn zero.
|
|
9
|
+
set -eu
|
|
10
|
+
|
|
11
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
12
|
+
# shellcheck source=_lib/sanitize.sh
|
|
13
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
14
|
+
# shellcheck source=_lib/state.sh
|
|
15
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
16
|
+
|
|
17
|
+
task_id="session-bootstrap"
|
|
18
|
+
operator_principal="local-operator"
|
|
19
|
+
force=0
|
|
20
|
+
|
|
21
|
+
while [ "$#" -gt 0 ]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
--task-id) task_id=$2; shift 2 ;;
|
|
24
|
+
--operator) operator_principal=$2; shift 2 ;;
|
|
25
|
+
--force) force=1; shift ;;
|
|
26
|
+
-h|--help)
|
|
27
|
+
cat <<'EOF'
|
|
28
|
+
session-init — prepare local workflow surface for a live seat session
|
|
29
|
+
|
|
30
|
+
sh bin/session-init [--task-id <id>] [--operator <id>] [--force]
|
|
31
|
+
|
|
32
|
+
Requires seat-init first. If seat-consume-pass already ran, the hosted
|
|
33
|
+
workspace_id is copied into session.json. This command is local-only: no
|
|
34
|
+
hosted POST, no product repo clone, no execution, no grant attachment.
|
|
35
|
+
EOF
|
|
36
|
+
exit 0
|
|
37
|
+
;;
|
|
38
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
39
|
+
esac
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
seat_file=$(etq_state_seat_path)
|
|
43
|
+
if [ ! -f "$seat_file" ]; then
|
|
44
|
+
echo "session-init: seat.json missing — run \`sh bin/seat-init\` first" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
seat_id=$(etq_state_read_key "$seat_file" seat_id) || exit 1
|
|
49
|
+
execution_seat=$(etq_state_read_key "$seat_file" execution_seat) || exit 1
|
|
50
|
+
seat_id=$(etq_sanitize_opaque_id "$seat_id") || exit 1
|
|
51
|
+
execution_seat=$(etq_sanitize_opaque_id "$execution_seat") || exit 1
|
|
52
|
+
operator_principal=$(etq_sanitize_opaque_id "$operator_principal") || exit 1
|
|
53
|
+
task_id=$(etq_sanitize_opaque_id "$task_id") || exit 1
|
|
54
|
+
|
|
55
|
+
etq_state_init_dir
|
|
56
|
+
session_file="$(etq_state_dir)/session.json"
|
|
57
|
+
if [ -f "$session_file" ] && [ "$force" -eq 0 ]; then
|
|
58
|
+
existing=$(etq_state_read_key "$session_file" session_id 2>/dev/null || true)
|
|
59
|
+
if [ -n "$existing" ]; then
|
|
60
|
+
printf 'session-init: session already exists (session_id=%s); use --force to re-open\n' "$existing"
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
mkdir -p \
|
|
66
|
+
"$ROOT/.etiquette/events" \
|
|
67
|
+
"$ROOT/docs/work/ledger" \
|
|
68
|
+
"$ROOT/docs/work/receipts" \
|
|
69
|
+
"$ROOT/docs/work/tasks" \
|
|
70
|
+
"$ROOT/docs/work/outbox" \
|
|
71
|
+
"$ROOT/docs/work/sessions"
|
|
72
|
+
|
|
73
|
+
ledger="$ROOT/docs/work/ledger/LEDGER.md"
|
|
74
|
+
if [ ! -f "$ledger" ]; then
|
|
75
|
+
printf '# Local Ledger\n\n' > "$ledger"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
ts=$(date +%s)
|
|
79
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
80
|
+
rand=$(openssl rand -hex 4)
|
|
81
|
+
elif [ -r /dev/urandom ]; then
|
|
82
|
+
rand=$(od -An -N4 -tx1 /dev/urandom | tr -d ' \n')
|
|
83
|
+
else
|
|
84
|
+
rand=$(awk 'BEGIN{srand(); printf "%08x", int(rand()*4294967295)}')
|
|
85
|
+
fi
|
|
86
|
+
session_id="session-${ts}-${rand}"
|
|
87
|
+
session_id=$(etq_sanitize_opaque_id "$session_id") || exit 1
|
|
88
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
89
|
+
|
|
90
|
+
workspace_id=""
|
|
91
|
+
hosted_host=""
|
|
92
|
+
workspace_file=$(etq_state_workspace_path)
|
|
93
|
+
if [ -f "$workspace_file" ]; then
|
|
94
|
+
workspace_id=$(etq_state_read_key "$workspace_file" workspace_id 2>/dev/null || true)
|
|
95
|
+
hosted_host=$(etq_state_read_key "$workspace_file" hosted_host 2>/dev/null || true)
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
body=$(jq -cn \
|
|
99
|
+
--arg session_id "$session_id" \
|
|
100
|
+
--arg started_at "$now" \
|
|
101
|
+
--arg seat_id "$seat_id" \
|
|
102
|
+
--arg execution_seat "$execution_seat" \
|
|
103
|
+
--arg operator_principal "$operator_principal" \
|
|
104
|
+
--arg task_id "$task_id" \
|
|
105
|
+
--arg remote_registration "$([ -n "$workspace_id" ] && printf present || printf absent)" \
|
|
106
|
+
--arg workspace_id "$workspace_id" \
|
|
107
|
+
--arg hosted_host "$hosted_host" \
|
|
108
|
+
'{
|
|
109
|
+
session_id: $session_id,
|
|
110
|
+
started_at: $started_at,
|
|
111
|
+
seat_id: $seat_id,
|
|
112
|
+
execution_seat: $execution_seat,
|
|
113
|
+
operator_principal: $operator_principal,
|
|
114
|
+
task_id: $task_id,
|
|
115
|
+
workflow_surface: "local-control-seat",
|
|
116
|
+
remote_registration: $remote_registration
|
|
117
|
+
}
|
|
118
|
+
+ (if $workspace_id != "" then {workspace_id: $workspace_id} else {} end)
|
|
119
|
+
+ (if $hosted_host != "" then {hosted_host: $hosted_host} else {} end)')
|
|
120
|
+
|
|
121
|
+
etq_state_write_file "$session_file" "$body"
|
|
122
|
+
printf -- '- %s session=%s task=%s seat=%s operator=%s action=session_opened scope=local-workflow remote_registration=%s authority=advisory-only\n' \
|
|
123
|
+
"$now" "$session_id" "$task_id" "$seat_id" "$operator_principal" \
|
|
124
|
+
"$([ -n "$workspace_id" ] && printf present || printf absent)" >> "$ledger"
|
|
125
|
+
|
|
126
|
+
sh "$ROOT/bin/local-event" "$task_id" "$seat_id" session_init \
|
|
127
|
+
"session $session_id opened for $execution_seat; local ledger/receipts/tasks/outbox ready" >/dev/null
|
|
128
|
+
|
|
129
|
+
printf 'session-init: opened session_id=%s task_id=%s seat_id=%s\n' "$session_id" "$task_id" "$seat_id"
|
|
130
|
+
printf 'session-init: wrote %s\n' "$session_file"
|
|
131
|
+
printf 'session-init: ledger ready at docs/work/ledger/LEDGER.md\n'
|
|
132
|
+
printf 'session-init: receipts/tasks/outbox ready under docs/work/\n'
|
package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Access Assurance
|
|
2
|
+
|
|
3
|
+
The vanilla control seat starts in `open` mode. That keeps Etiquette usable as a
|
|
4
|
+
local-first workflow kit without authentication, screening, hosted services, or
|
|
5
|
+
a daemon.
|
|
6
|
+
|
|
7
|
+
The workspace mode lives in `workspace-secure-profile.v0.json`.
|
|
8
|
+
|
|
9
|
+
Modes:
|
|
10
|
+
|
|
11
|
+
- `open`: no access-assurance gate is required.
|
|
12
|
+
- `screening-warn`: restricted-seat policy and claims are checked, but failures
|
|
13
|
+
warn instead of blocking.
|
|
14
|
+
- `screening-enforced`: restricted seats fail closed when policy or claims are
|
|
15
|
+
missing, expired, revoked, or not provider-backed.
|
|
16
|
+
- `enterprise-reactor`: `screening-enforced` plus an optional tighten-only
|
|
17
|
+
daemon for revocation sync, badge refresh, kill-switch writes, and outbox
|
|
18
|
+
drain.
|
|
19
|
+
|
|
20
|
+
The reactor may tighten access only. It must not grant access, authorize
|
|
21
|
+
actions, close work, merge, or rewrite the ledger.
|
|
22
|
+
|
|
23
|
+
Provider-backed secure deployments should keep raw identity material outside the
|
|
24
|
+
repo:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Persona / Scandit / SSO -> Supabase derived claims -> local access check
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Repo-safe fields are provider refs, status, policy eligibility class, evidence
|
|
31
|
+
hash, expiry, and audit timestamps. Passport images, document photos, full
|
|
32
|
+
document numbers, ethnicity, provider secrets, and raw webhook payloads do not
|
|
33
|
+
belong in Git.
|
|
34
|
+
|
|
35
|
+
Check the mode:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
sh bin/access-assurance-check
|
|
39
|
+
```
|
|
@@ -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
|
+
|