@etiquekit/etq 1.0.11 → 1.0.13
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 +19 -26
- package/LICENSE +2 -3
- package/NOTICE +6 -4
- package/QuickStart.md +35 -47
- package/README.md +54 -56
- package/bin/etiquette +1 -5
- package/bin/etiquette-core +1 -5
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/CODEX_CLIENT_COMPATIBILITY.md +1 -1
- package/docs/CONCEPTS.md +7 -23
- package/docs/CONCEPT_STATUS.md +66 -0
- package/docs/CORE_PROFILE.md +3 -5
- package/docs/LANE_PROVISIONING.md +7 -2
- package/docs/README.md +4 -4
- package/docs/RELEASE_SURFACE_AUDIT.md +7 -6
- package/docs/SEAT_DISCIPLINE.md +55 -91
- package/docs/SEAT_PROVISIONING.md +19 -22
- package/docs/TEAM_HANDOFF.md +21 -21
- package/docs/WORKTREE_QOL.md +7 -6
- package/docs/contracts/ledger-entry/README.md +11 -11
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +3 -3
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
- package/package.json +11 -11
- package/packages/control/src/authority/lease.ts +261 -0
- package/packages/control/src/authority/node-delegation.ts +257 -0
- package/packages/control/src/authority/rig-conductor.ts +632 -0
- package/packages/control/src/cli/argv.ts +155 -0
- package/packages/control/src/cli/commands/console.ts +200 -0
- package/packages/control/src/cli/commands/dispatch-core.ts +89 -0
- package/packages/control/src/cli/commands/dispatch.ts +279 -0
- package/packages/control/src/cli/commands/harness.ts +89 -0
- package/packages/control/src/cli/commands/hook.ts +50 -0
- package/packages/control/src/cli/commands/ledger.ts +91 -0
- package/packages/control/src/cli/commands/local-workflow.ts +4686 -0
- package/packages/control/src/cli/commands/memory.ts +445 -0
- package/packages/control/src/cli/commands/release.ts +108 -0
- package/packages/control/src/cli/commands/rubric.ts +103 -0
- package/packages/control/src/cli/commands/seat.ts +179 -0
- package/packages/control/src/cli/commands/session.ts +127 -0
- package/packages/control/src/cli/commands/supervision.ts +246 -0
- package/packages/control/src/cli/commands/sync.ts +119 -0
- package/packages/control/src/cli/commands/workflow.ts +86 -0
- package/packages/control/src/cli/commands/workspace.ts +50 -0
- package/packages/control/src/cli/core-usage.ts +34 -0
- package/packages/control/src/cli/prompt.ts +67 -0
- package/packages/control/src/cli/supervision-deps.ts +44 -0
- package/packages/control/src/cli/usage.ts +241 -0
- package/packages/control/src/cli.ts +207 -0
- package/packages/control/src/core-cli.ts +50 -0
- package/packages/control/src/dispatch/decision.ts +202 -0
- package/packages/control/src/dispatch/projection.ts +293 -0
- package/packages/control/src/dispatch/record.ts +153 -0
- package/packages/control/src/engagement/project.ts +170 -0
- package/packages/control/src/fs.ts +19 -0
- package/packages/control/src/harness/pruning.ts +406 -0
- package/packages/control/src/hooks/dispatcher.ts +117 -0
- package/packages/control/src/hooks/outbox.ts +86 -0
- package/packages/control/src/hooks/sanitize.ts +6 -0
- package/packages/control/src/hooks/types.ts +34 -0
- package/packages/control/src/index.ts +384 -0
- package/packages/control/src/ledger/entry.ts +303 -0
- package/packages/control/src/ledger/indexer.ts +542 -0
- package/packages/control/src/memory/context.ts +149 -0
- package/packages/control/src/memory/drain-import.ts +207 -0
- package/packages/control/src/memory/indexer.ts +284 -0
- package/packages/control/src/memory/query.ts +75 -0
- package/packages/control/src/memory/sanitize.ts +50 -0
- package/packages/control/src/memory/sharded-drain-import.ts +212 -0
- package/packages/control/src/memory/status.ts +211 -0
- package/packages/control/src/memory/store-lifecycle.ts +509 -0
- package/packages/control/src/memory/store.ts +284 -0
- package/packages/control/src/memory/types.ts +146 -0
- package/packages/control/src/parity/surfaces.ts +748 -0
- package/packages/control/src/project.ts +141 -0
- package/packages/control/src/projection/local-ledger-view.ts +373 -0
- package/packages/control/src/projection/return-enforcement.ts +48 -0
- package/packages/control/src/projection/timeline-preview.ts +539 -0
- package/packages/control/src/projection/timeline.ts +708 -0
- package/packages/control/src/release/readiness.ts +831 -0
- package/packages/control/src/rubric/loader.ts +326 -0
- package/packages/control/src/rubric/promotion.ts +54 -0
- package/packages/control/src/rubric/runner.ts +159 -0
- package/packages/control/src/rubric/types.ts +158 -0
- package/packages/control/src/seat/owner-card.ts +388 -0
- package/packages/control/src/seat/readiness.ts +834 -0
- package/packages/control/src/session/runbook.ts +431 -0
- package/packages/control/src/shared/sanitize.ts +49 -0
- package/packages/control/src/supervision/action-classes.ts +192 -0
- package/packages/control/src/supervision/command-apply.ts +378 -0
- package/packages/control/src/supervision/errors.ts +14 -0
- package/packages/control/src/supervision/event-replay.ts +155 -0
- package/packages/control/src/supervision/events.ts +109 -0
- package/packages/control/src/supervision/index.ts +16 -0
- package/packages/control/src/supervision/manifest.ts +127 -0
- package/packages/control/src/supervision/paths.ts +49 -0
- package/packages/control/src/supervision/projection-adapter.ts +274 -0
- package/packages/control/src/supervision/projection.ts +75 -0
- package/packages/control/src/supervision/rebuild.ts +99 -0
- package/packages/control/src/supervision/session-open.ts +131 -0
- package/packages/control/src/supervision/session-read.ts +99 -0
- package/packages/control/src/supervision/sqlite-impl.ts +71 -0
- package/packages/control/src/supervision/sqlite.ts +121 -0
- package/packages/control/src/supervision/store-rows.ts +371 -0
- package/packages/control/src/supervision/turn-close.ts +154 -0
- package/packages/control/src/supervision/turn-open.ts +284 -0
- package/packages/control/src/sync/event-log-merge-driver.ts +263 -0
- package/packages/control/src/sync/join-plan.ts +375 -0
- package/packages/control/src/sync/outbox.ts +492 -0
- package/packages/control/src/workflow/evaluator.ts +140 -0
- package/packages/control/src/workflow/loader.ts +200 -0
- package/packages/control/src/workflow/types.ts +90 -0
- package/packages/control/src/workspace/authority.ts +499 -0
- package/packages/protocol/src/guards.ts +119 -0
- package/packages/protocol/src/huddle-board.ts +198 -0
- package/packages/protocol/src/huddle.ts +295 -0
- package/packages/protocol/src/incident.ts +251 -0
- package/packages/protocol/src/index.ts +8 -0
- package/packages/protocol/src/interfaces.ts +107 -0
- package/packages/protocol/src/packet-profile.ts +195 -0
- package/packages/protocol/src/state.ts +81 -0
- package/packages/protocol/src/types.ts +434 -0
- package/release/lineage.v0.json +14 -0
- package/scripts/release-candidate-verify.sh +175 -0
- package/scripts/release-checksum.sh +25 -0
- package/scripts/release-pack-canary.sh +97 -0
- package/scripts/release-scan.sh +249 -0
- package/scripts/release-sign.sh +34 -0
- package/templates/etiquette-vanilla-v0/README.md +2 -3
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +0 -6
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +0 -5
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +0 -5
- package/templates/seat-packs-v0/README.md +3 -2
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -29
- package/templates/seat-packs-v0/source/codex-seat/README.md +3 -17
- package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -23
- package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
- package/docs/AGENT_SPAWNING.md +0 -78
- package/docs/INTEGRATION_UI.md +0 -215
- package/docs/OPERATOR_PLAYBOOK.md +0 -77
- package/docs/TAG_ROUTE.md +0 -95
- package/lib/etiquette-core.js +0 -315
- package/lib/etiquette.js +0 -1160
- package/templates/DISPATCH_PREMISES.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +0 -55
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +0 -53
- package/templates/hosted-receiver/README.md +0 -41
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +0 -129
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Dispatch Premises (Load-Bearing Premise Gate)
|
|
2
|
-
|
|
3
|
-
Reusable discipline block for dispatch cards. A load-bearing premise is any
|
|
4
|
-
claim whose falsity changes the dispatch's verdict, scope, or architecture.
|
|
5
|
-
Refuted premises waste a full runner round trip; the gate moves the check to
|
|
6
|
-
the dispatcher, where it is cheapest.
|
|
7
|
-
|
|
8
|
-
## Gate Rules
|
|
9
|
-
|
|
10
|
-
1. If a load-bearing premise is statically decidable at the routed base, the
|
|
11
|
-
dispatcher must VERIFY it before posting. Proof means a file:line whose
|
|
12
|
-
CONTENTS were read at the pinned base, on the CONSUMING surface's path.
|
|
13
|
-
Producer-side or sibling-binary evidence never verifies a consumer claim.
|
|
14
|
-
Name or path existence is never content.
|
|
15
|
-
2. Premises decidable only at runtime or by an external system ship as a
|
|
16
|
-
labeled HYPOTHESIS with a paired stop-condition the runner can trigger.
|
|
17
|
-
3. Budget: about 15 minutes and at most 5 premises. Beyond that, downgrade
|
|
18
|
-
the dispatch to a probe task whose deliverable IS the premise table.
|
|
19
|
-
4. Lint tell: a stop-condition that restates a static premise verbatim is a
|
|
20
|
-
fence instead of a check — verify it at dispatch time instead.
|
|
21
|
-
5. Runner stop-conditions remain the backstop, not the primary check. A
|
|
22
|
-
runner that finds a premise false must STOP and counter, never fabricate
|
|
23
|
-
a passing result.
|
|
24
|
-
|
|
25
|
-
## Template Block
|
|
26
|
-
|
|
27
|
-
Paste into the dispatch card and fill in:
|
|
28
|
-
|
|
29
|
-
```markdown
|
|
30
|
-
## Premises
|
|
31
|
-
|
|
32
|
-
| # | premise | class | proof / stop-condition |
|
|
33
|
-
| --- | --- | --- | --- |
|
|
34
|
-
| P1 | <claim the dispatch depends on> | VERIFIED | <file:line, contents read at <base-sha>, on the consuming surface> |
|
|
35
|
-
| P2 | <claim decidable only at runtime> | HYPOTHESIS | STOP if <observable falsifier>; counter instead of fabricating a pass |
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Every load-bearing premise appears in the table. A dispatch with an
|
|
39
|
-
unverified static premise is not ready to post.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
-
PROFILE="$ROOT/docs/work/access/workspace-secure-profile.v0.json"
|
|
6
|
-
POLICY="$ROOT/docs/work/access/seat-access-policy.v0.json"
|
|
7
|
-
CLAIMS="$ROOT/docs/work/access/access-assurance-claims.v0.json"
|
|
8
|
-
|
|
9
|
-
json_string_field() {
|
|
10
|
-
sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" "$1" | head -n 1
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
json_bool_field() {
|
|
14
|
-
sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\\(true\\|false\\).*/\\1/p" "$1" | head -n 1
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if [ ! -f "$PROFILE" ]; then
|
|
18
|
-
echo "access-assurance: fail missing docs/work/access/workspace-secure-profile.v0.json" >&2
|
|
19
|
-
exit 1
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
mode=$(json_string_field "$PROFILE" mode)
|
|
23
|
-
case "$mode" in
|
|
24
|
-
open|screening-warn|screening-enforced|enterprise-reactor) ;;
|
|
25
|
-
*)
|
|
26
|
-
echo "access-assurance: fail invalid mode=${mode:-missing}" >&2
|
|
27
|
-
exit 1
|
|
28
|
-
;;
|
|
29
|
-
esac
|
|
30
|
-
|
|
31
|
-
if [ "$mode" = "open" ]; then
|
|
32
|
-
echo "access-assurance: pass mode=open policy=not-required"
|
|
33
|
-
exit 0
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
if [ ! -f "$POLICY" ] || [ ! -f "$CLAIMS" ]; then
|
|
37
|
-
message="access-assurance: missing policy or claims for mode=$mode"
|
|
38
|
-
if [ "$mode" = "screening-warn" ]; then
|
|
39
|
-
echo "$message"
|
|
40
|
-
exit 0
|
|
41
|
-
fi
|
|
42
|
-
echo "$message" >&2
|
|
43
|
-
exit 1
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
if [ "$mode" = "enterprise-reactor" ]; then
|
|
47
|
-
tighten_only=$(json_bool_field "$PROFILE" tighten_only)
|
|
48
|
-
reactor_can_grant=$(json_bool_field "$PROFILE" reactor_can_grant_or_loosen_access)
|
|
49
|
-
if [ "$tighten_only" != "true" ] || [ "$reactor_can_grant" != "false" ]; then
|
|
50
|
-
echo "access-assurance: fail enterprise-reactor requires tighten_only=true and reactor_can_grant_or_loosen_access=false" >&2
|
|
51
|
-
exit 1
|
|
52
|
-
fi
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
echo "access-assurance: pass mode=$mode"
|
package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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
|
-
approved identity provider / assurance provider -> 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
|
-
```
|
|
@@ -1,53 +0,0 @@
|
|
|
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": "approved identity provider plus derived assurance 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
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Hosted Receiver Templates
|
|
2
|
-
|
|
3
|
-
Opt-in templates for teams that want to expose Etiquette's W1 GitHub webhook
|
|
4
|
-
receiver behind their own hosting layer.
|
|
5
|
-
|
|
6
|
-
## W1 GitHub Receiver
|
|
7
|
-
|
|
8
|
-
`w1-github-webhook-receiver.mjs` is a small Node HTTP server:
|
|
9
|
-
|
|
10
|
-
- `GET /health` returns a readiness probe.
|
|
11
|
-
- `POST /github` accepts GitHub `ping` and `push` webhook deliveries.
|
|
12
|
-
- `ping` returns `200` only after the delivery is signature-verified.
|
|
13
|
-
- `push` delegates to `etiquette notify webhook github`.
|
|
14
|
-
- raw payloads are written only to a temporary file for the CLI call, then
|
|
15
|
-
removed.
|
|
16
|
-
- emitted records stay pointer-only and authority-false.
|
|
17
|
-
|
|
18
|
-
Run it with:
|
|
19
|
-
|
|
20
|
-
```sh
|
|
21
|
-
export ETIQUETTE_GITHUB_WEBHOOK_SECRET='<webhook-secret>'
|
|
22
|
-
export ETIQUETTE_CLI='etiquette'
|
|
23
|
-
node w1-github-webhook-receiver.mjs
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Optional:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
export PORT=8080
|
|
30
|
-
export ETIQUETTE_WAKE_OUT_DIR=/var/lib/etiquette/wake
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`ETIQUETTE_WAKE_OUT_DIR` stores only the verified pointer output, never the raw
|
|
34
|
-
webhook body.
|
|
35
|
-
|
|
36
|
-
## Boundary
|
|
37
|
-
|
|
38
|
-
This template does not create a public endpoint, register repository webhooks,
|
|
39
|
-
provision cloud infrastructure, start remote execution, or grant authority. Host
|
|
40
|
-
selection, webhook registration, secret rotation, and retention policy remain
|
|
41
|
-
operator decisions.
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createServer } from 'node:http';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
8
|
-
|
|
9
|
-
const port = Number(process.env.PORT || '8080');
|
|
10
|
-
const etiquetteCli = process.env.ETIQUETTE_CLI || 'etiquette';
|
|
11
|
-
const secret = process.env.ETIQUETTE_GITHUB_WEBHOOK_SECRET;
|
|
12
|
-
const outDir = process.env.ETIQUETTE_WAKE_OUT_DIR || '';
|
|
13
|
-
|
|
14
|
-
if (!secret) throw new Error('ETIQUETTE_GITHUB_WEBHOOK_SECRET is required');
|
|
15
|
-
|
|
16
|
-
function readRaw(req) {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
const chunks = [];
|
|
19
|
-
req.on('data', (chunk) => chunks.push(chunk));
|
|
20
|
-
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
21
|
-
req.on('error', reject);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function writeJson(res, status, value) {
|
|
26
|
-
res.writeHead(status, {
|
|
27
|
-
'content-type': 'application/json',
|
|
28
|
-
'cache-control': 'no-store',
|
|
29
|
-
});
|
|
30
|
-
res.end(`${JSON.stringify(value, null, 2)}\n`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function publicRecord(record) {
|
|
34
|
-
return {
|
|
35
|
-
ok: true,
|
|
36
|
-
schema: record.schema,
|
|
37
|
-
event: record.event,
|
|
38
|
-
delivery_id: record.delivery_id,
|
|
39
|
-
repository: record.repository.full_name,
|
|
40
|
-
ref: record.ref || null,
|
|
41
|
-
after: record.after || null,
|
|
42
|
-
signature_verified: record.transport.signature_verified,
|
|
43
|
-
pointers_only: record.payload_boundary.pointers_only,
|
|
44
|
-
notification_is_authority: record.authority_boundary.notification_is_authority,
|
|
45
|
-
next_action: record.next_action,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function verifyDelivery(req, event, delivery, signature) {
|
|
50
|
-
const raw = await readRaw(req);
|
|
51
|
-
const payloadPath = join(tmpdir(), `etiquette-webhook-${delivery}-${randomUUID()}.json`);
|
|
52
|
-
writeFileSync(payloadPath, raw);
|
|
53
|
-
try {
|
|
54
|
-
const result = spawnSync(etiquetteCli, [
|
|
55
|
-
'notify',
|
|
56
|
-
'webhook',
|
|
57
|
-
'github',
|
|
58
|
-
'--payload',
|
|
59
|
-
payloadPath,
|
|
60
|
-
'--delivery',
|
|
61
|
-
delivery,
|
|
62
|
-
'--event',
|
|
63
|
-
event,
|
|
64
|
-
'--signature-256',
|
|
65
|
-
signature,
|
|
66
|
-
'--json',
|
|
67
|
-
], {
|
|
68
|
-
encoding: 'utf8',
|
|
69
|
-
env: {
|
|
70
|
-
...process.env,
|
|
71
|
-
ETIQUETTE_GITHUB_WEBHOOK_SECRET: secret,
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
if (result.status !== 0) {
|
|
75
|
-
return {
|
|
76
|
-
ok: false,
|
|
77
|
-
status: 401,
|
|
78
|
-
body: {
|
|
79
|
-
ok: false,
|
|
80
|
-
error: 'signature_or_payload_rejected',
|
|
81
|
-
detail: result.stderr.trim().slice(0, 200),
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
const record = JSON.parse(result.stdout);
|
|
86
|
-
if (outDir) {
|
|
87
|
-
mkdirSync(outDir, { recursive: true });
|
|
88
|
-
writeFileSync(join(outDir, 'latest.json'), `${JSON.stringify(record, null, 2)}\n`);
|
|
89
|
-
writeFileSync(join(outDir, `${delivery}.json`), `${JSON.stringify(record, null, 2)}\n`);
|
|
90
|
-
}
|
|
91
|
-
return { ok: true, status: 200, body: publicRecord(record) };
|
|
92
|
-
} finally {
|
|
93
|
-
rmSync(payloadPath, { force: true });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const server = createServer(async (req, res) => {
|
|
98
|
-
try {
|
|
99
|
-
if (req.method === 'GET' && req.url === '/health') {
|
|
100
|
-
writeJson(res, 200, { ok: true, authority: false });
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (req.method !== 'POST' || req.url !== '/github') {
|
|
104
|
-
writeJson(res, 404, { ok: false, error: 'not_found' });
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const event = String(req.headers['x-github-event'] || '');
|
|
109
|
-
const delivery = String(req.headers['x-github-delivery'] || randomUUID());
|
|
110
|
-
const signature = String(req.headers['x-hub-signature-256'] || '');
|
|
111
|
-
if (event !== 'push' && event !== 'ping') {
|
|
112
|
-
writeJson(res, 400, { ok: false, error: 'unsupported_event', event });
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (!signature.startsWith('sha256=')) {
|
|
116
|
-
writeJson(res, 401, { ok: false, error: 'missing_signature' });
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const result = await verifyDelivery(req, event, delivery, signature);
|
|
121
|
-
writeJson(res, result.status, result.body);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
writeJson(res, 500, { ok: false, error: String(error?.message || error) });
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
server.listen(port, '0.0.0.0', () => {
|
|
128
|
-
console.log(`etiquette_w1_receiver: listening on ${port}`);
|
|
129
|
-
});
|