@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.
Files changed (83) hide show
  1. package/AGENTS.md +28 -0
  2. package/QuickStart.md +86 -0
  3. package/README.md +77 -9
  4. package/bin/etiquette +1150 -0
  5. package/bin/etiquette-core +313 -0
  6. package/docs/ARCHITECTURE.md +100 -0
  7. package/docs/CODEX_CLIENT_COMPATIBILITY.md +83 -0
  8. package/docs/CONCEPTS.md +128 -0
  9. package/docs/CONCEPT_STATUS.md +67 -0
  10. package/docs/CORE_PROFILE.md +70 -0
  11. package/docs/LANE_PROVISIONING.md +157 -0
  12. package/docs/README.md +29 -0
  13. package/docs/RELEASE_SURFACE_AUDIT.md +156 -0
  14. package/docs/SEAT_DISCIPLINE.md +116 -0
  15. package/docs/SEAT_PROVISIONING.md +100 -0
  16. package/docs/TEAM_HANDOFF.md +190 -0
  17. package/docs/WORKTREE_QOL.md +92 -0
  18. package/docs/contracts/ledger-entry/README.md +83 -0
  19. package/docs/contracts/ledger-entry/ledger-entry.v0.1.md +116 -0
  20. package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +90 -0
  21. package/docs/contracts/ledger-entry/ledger-entry.v0.md +123 -0
  22. package/docs/contracts/ledger-entry/ledger-query-cli.v0.1.md +64 -0
  23. package/package.json +42 -11
  24. package/scripts/install.sh +144 -0
  25. package/scripts/uninstall.sh +58 -0
  26. package/templates/etiquette-vanilla-v0/README.md +69 -0
  27. package/templates/etiquette-vanilla-v0/bin/stamp-vanilla +33 -0
  28. package/templates/etiquette-vanilla-v0/source/control-seat/.llm-substrate/HANDOFF.md +22 -0
  29. package/templates/etiquette-vanilla-v0/source/control-seat/README.md +16 -0
  30. package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/http.sh +121 -0
  31. package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/sanitize.sh +191 -0
  32. package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/state.sh +67 -0
  33. package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
  34. package/templates/etiquette-vanilla-v0/source/control-seat/bin/compact-local-events +71 -0
  35. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/post-tool-use +64 -0
  36. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-end +64 -0
  37. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-start +64 -0
  38. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/stop +64 -0
  39. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/user-prompt-submit +64 -0
  40. package/templates/etiquette-vanilla-v0/source/control-seat/bin/local-event +53 -0
  41. package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-receipt +143 -0
  42. package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-telemetry +112 -0
  43. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-consume-pass +115 -0
  44. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +20 -0
  45. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-init +86 -0
  46. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-post-readiness +171 -0
  47. package/templates/etiquette-vanilla-v0/source/control-seat/bin/session-init +132 -0
  48. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
  49. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
  50. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/ledger/LEDGER.md +7 -0
  51. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/playbacks/PLAYBACK_TEMPLATE.md +31 -0
  52. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/receipts/.gitkeep +1 -0
  53. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/tasks/CONTROL-001.yaml +13 -0
  54. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/README.md +6 -0
  55. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/bin/kernel-doctor +11 -0
  56. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/playback.v0.md +37 -0
  57. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/receipt.v0.md +25 -0
  58. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/seat-readiness.v0.md +22 -0
  59. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/task.v0.md +18 -0
  60. package/templates/etiquette-vanilla-v0/validate-vanilla.sh +43 -0
  61. package/templates/github-actions/README.md +30 -0
  62. package/templates/github-actions/w1-webhook-wake-canary.yml +111 -0
  63. package/templates/hosted-receiver/README.md +41 -0
  64. package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
  65. package/templates/seat-packs-v0/README.md +72 -0
  66. package/templates/seat-packs-v0/bin/provision-seat +70 -0
  67. package/templates/seat-packs-v0/source/claude-code-seat/.llm-substrate/HANDOFF.md +24 -0
  68. package/templates/seat-packs-v0/source/claude-code-seat/README.md +19 -0
  69. package/templates/seat-packs-v0/source/claude-code-seat/bin/seat-doctor +8 -0
  70. package/templates/seat-packs-v0/source/codex-seat/.llm-substrate/HANDOFF.md +25 -0
  71. package/templates/seat-packs-v0/source/codex-seat/README.md +22 -0
  72. package/templates/seat-packs-v0/source/codex-seat/bin/seat-doctor +8 -0
  73. package/templates/seat-packs-v0/source/gemini-seat/.llm-substrate/HANDOFF.md +23 -0
  74. package/templates/seat-packs-v0/source/gemini-seat/README.md +19 -0
  75. package/templates/seat-packs-v0/source/gemini-seat/bin/seat-doctor +8 -0
  76. package/templates/seat-packs-v0/source/ollama-seat/.llm-substrate/HANDOFF.md +24 -0
  77. package/templates/seat-packs-v0/source/ollama-seat/README.md +19 -0
  78. package/templates/seat-packs-v0/source/ollama-seat/bin/seat-doctor +8 -0
  79. package/templates/seat-packs-v0/source/openrouter-seat/.llm-substrate/HANDOFF.md +24 -0
  80. package/templates/seat-packs-v0/source/openrouter-seat/README.md +19 -0
  81. package/templates/seat-packs-v0/source/openrouter-seat/bin/seat-doctor +8 -0
  82. package/templates/seat-packs-v0/validate-seat-packs.sh +27 -0
  83. package/bin/etq +0 -4
@@ -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"
@@ -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'
@@ -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
+ ```