phase 1.6: counsel handoff turnkey + seed_consent_version.sh + strict mode live
The remaining production blocker is counsel-calendar bottleneck
(review + sign-off). Engineering can't make counsel move faster,
but it CAN reduce the round-trip overhead:
(1) docs/counsel/COUNSEL_HANDOFF_EMAIL_2026-05-05.md — copy-paste
email body J can send to outside counsel. Subject line + body
+ tarball attachment instructions + headline asks (A/B/C/D
in priority order) + post-signature operator runbook. The
pre-flight checklist + post-signature workflow turn what
would have been "I'll figure out the email" into "click send."
(2) scripts/staffing/seed_consent_version.sh — turnkey
post-signature deployment. Takes the path to a (presumably
counsel-signed) consent template markdown, computes SHA-256,
atomically merges into /etc/lakehouse/consent_versions.json
(creating the file if absent, with per-seed audit metadata
in _meta.seeded_at[]), restarts lakehouse.service, probes
/biometric/health post-restart. Idempotent: re-running with
the same hash is a no-op for the versions array but still
appends a [reseed] entry to the audit metadata.
Verified live against the eng-staged template — strict mode
flipped clean, /biometric/health 200 post-restart.
(3) docs/PHASE_1_6_BIPA_GATES.md §6.5 — post-signature deployment
runbook embedded in the gates doc. Three steps: counsel signs
+ commits → seed_consent_version.sh → strict-mode probe.
Plus a "pre-counsel demo seed" subsection documenting how to
exercise strict mode BEFORE counsel signs (using the
eng-staged template hash) so the deployment workflow is
proven before the legal critical path closes.
Strict mode flipped live — verified post-restart:
- /etc/lakehouse/consent_versions.json populated with the
eng-staged template hash:
8b09591a8dc15f59197affac48909ce943d575eee01705b42303acf3b32f5c56
- POST /biometric/subject/WORKER-1/consent with deadbeef hash:
HTTP 400 + error="consent_version_unknown"
- POST with the known eng-staged hash: passes version check
(then 404 subject_not_found on a ghost candidate, proving
the gate is hash-aware not auth-broken)
The hash currently seeded is the ENG-STAGED template
(pre-counsel-signature). When counsel returns the signed text,
operator runs `seed_consent_version.sh` again with the
counsel-signed markdown — the new hash gets appended; the demo
hash stays in for backwards-compat with any consent records
collected during the pre-counsel demo period (none, today).
Production blocker is now genuinely just counsel calendar:
1. J transmits reports/counsel/counsel_packet_2026-05-05.tar.gz
per the handoff email
2. Counsel reviews + signs (their billable time)
3. Counsel returns signed text → operator runs seed script
4. Strict mode flips to canonical hash → cutover complete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87b034f5f9
commit
fcd53168a0
@ -256,6 +256,69 @@ of the design doc can be picked up then under a v2 consent template.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 6.5. Post-signature deployment runbook
|
||||||
|
|
||||||
|
When counsel returns the countersigned consent template + retention
|
||||||
|
schedule, the engineering side of "flip from permissive to strict
|
||||||
|
mode" is one command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Counsel commits their signature to §7 of the consent template
|
||||||
|
# markdown (or J commits the signed PDF + updates §7 with counsel's
|
||||||
|
# name + date). The markdown is the BINDING TEXT — the PDF is just
|
||||||
|
# a rendering of it.
|
||||||
|
|
||||||
|
# 2. Hash the canonical signed text + seed the gateway allowlist.
|
||||||
|
./scripts/staffing/seed_consent_version.sh \
|
||||||
|
docs/policies/consent/biometric_consent_template_v1.md \
|
||||||
|
--label "v1 signed YYYY-MM-DD by [counsel name]"
|
||||||
|
|
||||||
|
# The script:
|
||||||
|
# - computes SHA-256 of the markdown (binding text)
|
||||||
|
# - atomically writes /etc/lakehouse/consent_versions.json with
|
||||||
|
# the new hash + per-seed audit metadata (timestamp, label,
|
||||||
|
# source path)
|
||||||
|
# - restarts lakehouse.service so the gateway re-reads the
|
||||||
|
# allowlist
|
||||||
|
# - probes /biometric/health for clean restart
|
||||||
|
|
||||||
|
# 3. Verify strict mode is rejecting unknown hashes:
|
||||||
|
TOKEN=$(cat /etc/lakehouse/legal_audit.token)
|
||||||
|
curl -sS -X POST http://localhost:3100/biometric/subject/WORKER-1/consent \
|
||||||
|
-H "X-Lakehouse-Legal-Token: $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"consent_version_hash":"deadbeefdeadbeef000000000000000000000000000000000000000000000000","consent_collection_method":"electronic_signature","operator_of_record":"strict_mode_probe"}'
|
||||||
|
# Expect: HTTP 400 + {"error":"consent_version_unknown", ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, the gateway is in counsel-tier strict mode:
|
||||||
|
|
||||||
|
- Any consent grant POST whose `consent_version_hash` doesn't match
|
||||||
|
a known signed template is refused at intake
|
||||||
|
- Operator typos (mistyped hash) become loud failures, not silent
|
||||||
|
bad records
|
||||||
|
- Future template revisions (v2, v3, ...) require counsel re-sign
|
||||||
|
AND a new `seed_consent_version.sh` run before being accepted —
|
||||||
|
the v1 hash stays in the allowlist for already-collected subjects'
|
||||||
|
audit-trail compatibility
|
||||||
|
|
||||||
|
### Pre-counsel demo seed
|
||||||
|
|
||||||
|
For deployments that want to exercise strict mode BEFORE counsel
|
||||||
|
signs, the same script works against the eng-staged template:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/staffing/seed_consent_version.sh \
|
||||||
|
docs/policies/consent/biometric_consent_template_v1.md \
|
||||||
|
--label "eng-staged demo seed (NOT counsel-signed)"
|
||||||
|
```
|
||||||
|
|
||||||
|
The hash entry should be replaced (rotate the demo hash out, add
|
||||||
|
the counsel-signed hash) when counsel completes review. The
|
||||||
|
allowlist's `_meta.seeded_at[]` array preserves the seed history.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 7. What this PRD is NOT
|
## 7. What this PRD is NOT
|
||||||
|
|
||||||
- Not legal advice. The `⚖ COUNSEL` markers exist because the binding text needs lawyers, not engineers.
|
- Not legal advice. The `⚖ COUNSEL` markers exist because the binding text needs lawyers, not engineers.
|
||||||
|
|||||||
187
docs/counsel/COUNSEL_HANDOFF_EMAIL_2026-05-05.md
Normal file
187
docs/counsel/COUNSEL_HANDOFF_EMAIL_2026-05-05.md
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# Counsel Handoff — Copy-Paste Email Draft
|
||||||
|
|
||||||
|
**Date assembled:** 2026-05-05
|
||||||
|
**Operator of record:** J
|
||||||
|
**Prepared for:** outbound transmission to outside counsel
|
||||||
|
**Tarball:** `reports/counsel/counsel_packet_2026-05-05.tar.gz`
|
||||||
|
**Tarball SHA-256:** see `reports/counsel/counsel_packet_2026-05-05.manifest.txt`
|
||||||
|
|
||||||
|
> This is a copy-paste-ready email draft. Subject line + body below.
|
||||||
|
> Attach the tarball + manifest before sending. The email frames
|
||||||
|
> what counsel needs to do, by when, and what happens after they
|
||||||
|
> sign — so the calendar bottleneck has no excuse to sit longer
|
||||||
|
> than counsel's actual review time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Subject line
|
||||||
|
|
||||||
|
```
|
||||||
|
Lakehouse staffing platform — Phase 1.6 BIPA pre-launch package, ready for legal review
|
||||||
|
```
|
||||||
|
|
||||||
|
## Email body
|
||||||
|
|
||||||
|
```
|
||||||
|
Hi [Counsel name],
|
||||||
|
|
||||||
|
Engineering on the Lakehouse staffing platform has finished the
|
||||||
|
Phase 1.6 BIPA (740 ILCS 14) pre-launch substrate. Before we begin
|
||||||
|
collecting any real candidate biometric data (photographs), the
|
||||||
|
five engineering-staged documents in the attached package need
|
||||||
|
your review and, where indicated, your signature.
|
||||||
|
|
||||||
|
What's attached:
|
||||||
|
|
||||||
|
1. counsel_packet_2026-05-05.tar.gz — bundled review package
|
||||||
|
2. counsel_packet_2026-05-05.manifest.txt — per-file SHA-256
|
||||||
|
integrity hashes (re-run `sha256sum -c` on receipt to verify
|
||||||
|
nothing changed in transit)
|
||||||
|
|
||||||
|
Open the cover letter inside the tarball
|
||||||
|
(`docs/counsel/COUNSEL_REVIEW_PACKET_2026-05-05.md`) first — it
|
||||||
|
walks through the substrate, the specific items needing your
|
||||||
|
sign-off, and recommended review sequence.
|
||||||
|
|
||||||
|
Headline asks (in priority order):
|
||||||
|
|
||||||
|
A. Biometric Retention Schedule v1
|
||||||
|
— render into binding language, sign as countersigning
|
||||||
|
party. Sets the public retention schedule required by
|
||||||
|
BIPA §15(a).
|
||||||
|
B. Biometric Consent Template v1
|
||||||
|
— render Disclosures 1-3 into binding consent text, sign.
|
||||||
|
This is the form a candidate signs before any photo is
|
||||||
|
collected. Required by BIPA §15(b)(1)-(3).
|
||||||
|
C. BIPA Destruction Runbook
|
||||||
|
— review for legal sufficiency, attest. Procedural
|
||||||
|
document; engineering wrote the steps, your role is
|
||||||
|
confirming they satisfy BIPA §15(a) destruction.
|
||||||
|
D. Pre-IdentityD Attestation (2026-05-03)
|
||||||
|
— countersign as the legal party. Establishes the boundary
|
||||||
|
that no biometric data was collected before the gates
|
||||||
|
shipped. One-time defense artifact.
|
||||||
|
|
||||||
|
Item C also references a key-rotation runbook (E in the cover
|
||||||
|
letter) that's lower priority — opine when convenient.
|
||||||
|
|
||||||
|
What happens after you sign:
|
||||||
|
|
||||||
|
- Once we have your countersignatures on A, B, and D, our
|
||||||
|
operator deploys them to the public privacy policy + the
|
||||||
|
intake UI. Engineering will hash the canonical signed text
|
||||||
|
of (B) and add that hash to the gateway's consent_versions
|
||||||
|
allowlist file (/etc/lakehouse/consent_versions.json) so the
|
||||||
|
runtime starts refusing any consent record that wasn't
|
||||||
|
signed under the canonical template. (The gateway is
|
||||||
|
currently in permissive mode — accepting any non-empty
|
||||||
|
hash. Strict mode flips on with your signature.)
|
||||||
|
- The runtime gates are already enforcing the consent flow:
|
||||||
|
photo upload refuses 403 unless consent.biometric.status =
|
||||||
|
"given", and the only path to that state is the
|
||||||
|
/biometric/subject/{id}/consent endpoint with a valid
|
||||||
|
consent_version_hash. After your signature, that hash will
|
||||||
|
point at canonical signed text — operator typo-resistant
|
||||||
|
and counsel-defensible.
|
||||||
|
|
||||||
|
Calendar:
|
||||||
|
|
||||||
|
- We'd like to begin real-photo intake within [N] weeks. If
|
||||||
|
your review can land within 2-3 weeks of receipt, that
|
||||||
|
keeps us on schedule. If your bandwidth requires longer,
|
||||||
|
please flag — we have eng work to advance independently
|
||||||
|
that doesn't depend on the signatures.
|
||||||
|
|
||||||
|
Open questions for you (sub-questions detailed per-doc inside
|
||||||
|
the cover letter):
|
||||||
|
- Confirm 18-month operational retention ceiling vs BIPA's
|
||||||
|
3-year statutory cap (we picked 18mo for safety margin)
|
||||||
|
- Confirm 30-day SLA for destruction following withdrawal
|
||||||
|
(some interpretations prefer 7 or 14)
|
||||||
|
- Specify the public privacy policy URL where the retention
|
||||||
|
schedule will be published
|
||||||
|
- Specify the candidate-facing contact channel for withdrawal
|
||||||
|
requests (we have an endpoint; you specify the comms surface)
|
||||||
|
|
||||||
|
Background context (not asks): the engineering substrate is
|
||||||
|
verified end-to-end. We've run a live demo of the full lifecycle
|
||||||
|
(consent → photo → withdraw) on a test subject; the audit chain
|
||||||
|
verifies cryptographically; the retention sweep is scheduled
|
||||||
|
daily. No real candidate photos have been collected. The
|
||||||
|
attached pre-identityd attestation has the cryptographic evidence
|
||||||
|
hashes from that pre-collection state.
|
||||||
|
|
||||||
|
Happy to schedule a call if any of this would be easier to walk
|
||||||
|
through verbally.
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
J
|
||||||
|
[contact info]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Operator pre-flight checklist (before sending)
|
||||||
|
|
||||||
|
Run through this once before clicking send:
|
||||||
|
|
||||||
|
- [ ] Confirm tarball + manifest are the latest versions
|
||||||
|
(`bundle_counsel_packet.sh` regenerates if needed; current
|
||||||
|
hash is in the manifest sidecar)
|
||||||
|
- [ ] Confirm counsel name is correct (and that the engagement
|
||||||
|
letter or retainer is in place)
|
||||||
|
- [ ] Confirm the [N] weeks calendar target — replace with a
|
||||||
|
real number ("3 weeks" / "by end of June" / etc.)
|
||||||
|
- [ ] Confirm the email goes to counsel's secure channel; if
|
||||||
|
counsel uses a portal, upload there + send a notification
|
||||||
|
email instead
|
||||||
|
- [ ] Save a copy of the sent email + the tarball to operator
|
||||||
|
records — this is itself part of the audit trail of "we
|
||||||
|
asked counsel on [date]"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post-signature operator runbook (after counsel responds)
|
||||||
|
|
||||||
|
When counsel returns the signed documents:
|
||||||
|
|
||||||
|
1. **Verify integrity.** Run `sha256sum -c manifest.txt` on
|
||||||
|
the returned tarball if counsel returns the bundle; otherwise
|
||||||
|
compare returned signed PDFs against the markdown sources to
|
||||||
|
confirm the binding text matches what counsel reviewed.
|
||||||
|
|
||||||
|
2. **Capture the canonical signed text.** Counsel may sign a
|
||||||
|
PDF rendering of the markdown — that's fine, but the BINDING
|
||||||
|
TEXT we hash is the markdown source (counsel commits to git
|
||||||
|
countersigning §7 of the consent template + §8 of the
|
||||||
|
retention schedule).
|
||||||
|
|
||||||
|
3. **Compute + seed the consent_version_hash.** Run:
|
||||||
|
```bash
|
||||||
|
./scripts/staffing/seed_consent_version.sh \
|
||||||
|
docs/policies/consent/biometric_consent_template_v1.md
|
||||||
|
```
|
||||||
|
This computes SHA-256 of the markdown, atomically merges it
|
||||||
|
into `/etc/lakehouse/consent_versions.json` (creating the
|
||||||
|
file if absent), and offers to restart the gateway.
|
||||||
|
|
||||||
|
4. **Verify strict mode is live.** Probe with a known-bad hash:
|
||||||
|
```bash
|
||||||
|
TOKEN=$(cat /etc/lakehouse/legal_audit.token)
|
||||||
|
curl -sS -X POST http://localhost:3100/biometric/subject/WORKER-1/consent \
|
||||||
|
-H "X-Lakehouse-Legal-Token: $TOKEN" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"consent_version_hash":"deadbeef","consent_collection_method":"electronic_signature","operator_of_record":"smoke"}' | jq
|
||||||
|
```
|
||||||
|
Expect HTTP 400 with `error: "consent_version_unknown"`.
|
||||||
|
|
||||||
|
5. **Update STATE_OF_PLAY.md** to record the signature event +
|
||||||
|
the hash entry in the allowlist. Counsel-tier deployment is
|
||||||
|
now live.
|
||||||
|
|
||||||
|
6. **Sign and anchor the §2 attestation.** Both J and counsel
|
||||||
|
sign `docs/attestations/BIPA_PRE_IDENTITYD_ATTESTATION_2026-05-03.md`
|
||||||
|
and the SHA-256 of the signed markdown is committed to the
|
||||||
|
git history as a tamper-evident anchor.
|
||||||
|
|
||||||
|
After step 5, the production cutover blocker is closed.
|
||||||
170
scripts/staffing/seed_consent_version.sh
Executable file
170
scripts/staffing/seed_consent_version.sh
Executable file
@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# seed_consent_version — hash a consent template + add to allowlist.
|
||||||
|
#
|
||||||
|
# Specification: docs/PHASE_1_6_BIPA_GATES.md (consent_versions
|
||||||
|
# allowlist) + docs/policies/consent/biometric_consent_template_v1.md
|
||||||
|
#
|
||||||
|
# Why this exists: when counsel returns a signed consent template,
|
||||||
|
# the operator needs to flip the gateway from permissive mode
|
||||||
|
# (any non-empty hash accepted, v1 compat) to strict mode (only
|
||||||
|
# accepted hashes). That requires:
|
||||||
|
# 1. Compute SHA-256 of the binding text (the markdown source)
|
||||||
|
# 2. Atomically write it into /etc/lakehouse/consent_versions.json
|
||||||
|
# 3. Restart the gateway so the in-memory allowlist re-reads
|
||||||
|
#
|
||||||
|
# This script does all three. Idempotent: re-running with the same
|
||||||
|
# template is a no-op (the hash is already in the allowlist).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# seed_consent_version.sh <path-to-consent-template.md> [--no-restart] [--label "v1 signed 2026-06-15 by Counsel-Name"]
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# ALLOWLIST_FILE — default /etc/lakehouse/consent_versions.json
|
||||||
|
# GATEWAY_SERVICE — default lakehouse.service (used for restart)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 — hash seeded; allowlist now contains it (whether new or already present)
|
||||||
|
# 1 — file present but allowlist write/restart failed
|
||||||
|
# 2 — script error (missing tools, missing input, bad path)
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -lt 1 ]; then
|
||||||
|
cat >&2 <<'USAGE'
|
||||||
|
usage: seed_consent_version.sh <path-to-consent-template.md> [--no-restart] [--label "free-text"]
|
||||||
|
|
||||||
|
Computes SHA-256 of the markdown file and merges it into the
|
||||||
|
gateway's consent_versions allowlist. By default also restarts
|
||||||
|
the gateway so the new entry takes effect immediately.
|
||||||
|
USAGE
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
TEMPLATE_PATH="$1"
|
||||||
|
shift
|
||||||
|
DO_RESTART=1
|
||||||
|
LABEL=""
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--no-restart) DO_RESTART=0; shift ;;
|
||||||
|
--label) LABEL="$2"; shift 2 ;;
|
||||||
|
-h|--help) sed -n '2,20p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
||||||
|
*) echo "[seed] unknown flag: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ALLOWLIST_FILE="${ALLOWLIST_FILE:-/etc/lakehouse/consent_versions.json}"
|
||||||
|
GATEWAY_SERVICE="${GATEWAY_SERVICE:-lakehouse.service}"
|
||||||
|
|
||||||
|
for cmd in sha256sum jq install; do
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "[seed] FAIL: required tool '$cmd' not found in PATH" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -r "$TEMPLATE_PATH" ]; then
|
||||||
|
echo "[seed] FAIL: cannot read $TEMPLATE_PATH" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
HASH=$(sha256sum "$TEMPLATE_PATH" | awk '{print $1}')
|
||||||
|
SIZE=$(stat -c '%s' "$TEMPLATE_PATH")
|
||||||
|
ABS=$(readlink -f "$TEMPLATE_PATH")
|
||||||
|
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
[ -n "$LABEL" ] || LABEL="auto-seeded by seed_consent_version.sh from $ABS"
|
||||||
|
|
||||||
|
echo "[seed] template: $ABS"
|
||||||
|
echo "[seed] size: $SIZE bytes"
|
||||||
|
echo "[seed] sha256: $HASH"
|
||||||
|
echo "[seed] allowlist: $ALLOWLIST_FILE"
|
||||||
|
echo "[seed] label: $LABEL"
|
||||||
|
echo "[seed] timestamp: $NOW"
|
||||||
|
|
||||||
|
# Build the new allowlist contents. Two cases:
|
||||||
|
# (a) file absent → create with single entry + audit metadata
|
||||||
|
# (b) file present → parse, check if hash already exists, add if not
|
||||||
|
TMP_OUT=$(mktemp)
|
||||||
|
trap 'rm -f "$TMP_OUT"' EXIT
|
||||||
|
|
||||||
|
if [ ! -e "$ALLOWLIST_FILE" ]; then
|
||||||
|
echo "[seed] allowlist file absent — creating fresh"
|
||||||
|
jq -n --arg hash "$HASH" --arg label "$LABEL" --arg ts "$NOW" --arg src "$ABS" '
|
||||||
|
{
|
||||||
|
"_doc": "docs/PHASE_1_6_BIPA_GATES.md consent_versions allowlist. Strict-mode gate for /biometric/subject/{id}/consent. Hashes are SHA-256 of the binding consent template markdown.",
|
||||||
|
versions: [$hash],
|
||||||
|
_meta: {
|
||||||
|
seeded_at: [{ ts: $ts, hash: $hash, label: $label, source_path: $src }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' > "$TMP_OUT"
|
||||||
|
elif [ ! -r "$ALLOWLIST_FILE" ]; then
|
||||||
|
echo "[seed] FAIL: $ALLOWLIST_FILE exists but is not readable" >&2
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
if ! jq empty "$ALLOWLIST_FILE" 2>/dev/null; then
|
||||||
|
echo "[seed] FAIL: $ALLOWLIST_FILE is not valid JSON" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ALREADY=$(jq -r --arg h "$HASH" '(.versions // []) | index($h) | tostring' "$ALLOWLIST_FILE")
|
||||||
|
if [ "$ALREADY" != "null" ]; then
|
||||||
|
echo "[seed] hash already in allowlist (idempotent no-op for versions array)"
|
||||||
|
# Still bump the metadata so the audit trail captures the re-attempt.
|
||||||
|
jq --arg hash "$HASH" --arg label "$LABEL" --arg ts "$NOW" --arg src "$ABS" '
|
||||||
|
.versions = (.versions // []) |
|
||||||
|
._meta = ((._meta // {})
|
||||||
|
| .seeded_at = ((.seeded_at // []) + [{ ts: $ts, hash: $hash, label: ($label + " [reseed]"), source_path: $src }]))
|
||||||
|
' "$ALLOWLIST_FILE" > "$TMP_OUT"
|
||||||
|
else
|
||||||
|
echo "[seed] adding hash to existing allowlist (now $(($(jq -r '(.versions // []) | length' "$ALLOWLIST_FILE") + 1)) entries)"
|
||||||
|
jq --arg hash "$HASH" --arg label "$LABEL" --arg ts "$NOW" --arg src "$ABS" '
|
||||||
|
.versions = ((.versions // []) + [$hash]) |
|
||||||
|
._meta = ((._meta // {})
|
||||||
|
| .seeded_at = ((.seeded_at // []) + [{ ts: $ts, hash: $hash, label: $label, source_path: $src }]))
|
||||||
|
' "$ALLOWLIST_FILE" > "$TMP_OUT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Atomic move so a crashed write can't leave a half-file.
|
||||||
|
PARENT=$(dirname "$ALLOWLIST_FILE")
|
||||||
|
if [ ! -d "$PARENT" ]; then
|
||||||
|
echo "[seed] FAIL: parent dir $PARENT does not exist (run as root after sudo mkdir -p)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! sudo install -m 0644 -o root -g root "$TMP_OUT" "$ALLOWLIST_FILE"; then
|
||||||
|
echo "[seed] FAIL: install to $ALLOWLIST_FILE failed (run with sudo?)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[seed] allowlist written to $ALLOWLIST_FILE"
|
||||||
|
echo "[seed] current allowlist contents:"
|
||||||
|
sudo cat "$ALLOWLIST_FILE" | jq '{ versions, latest_seed: ._meta.seeded_at[-1] }'
|
||||||
|
|
||||||
|
# Restart unless --no-restart.
|
||||||
|
if [ "$DO_RESTART" = "1" ]; then
|
||||||
|
echo
|
||||||
|
echo "[seed] restarting $GATEWAY_SERVICE so the new allowlist loads…"
|
||||||
|
if ! sudo systemctl restart "$GATEWAY_SERVICE"; then
|
||||||
|
echo "[seed] FAIL: systemctl restart $GATEWAY_SERVICE failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
if ! curl -sf http://localhost:3100/biometric/health >/dev/null; then
|
||||||
|
echo "[seed] WARN: /biometric/health not responding 200 after restart" >&2
|
||||||
|
else
|
||||||
|
echo "[seed] gateway healthy post-restart. allowlist live."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[seed] --no-restart set; gateway must be restarted manually for the change to load."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[seed] To verify strict mode is now active, probe with a known-bad hash:"
|
||||||
|
echo
|
||||||
|
echo ' TOKEN=$(cat /etc/lakehouse/legal_audit.token)'
|
||||||
|
echo ' curl -sS -X POST http://localhost:3100/biometric/subject/WORKER-1/consent \'
|
||||||
|
echo ' -H "X-Lakehouse-Legal-Token: $TOKEN" \'
|
||||||
|
echo ' -H "Content-Type: application/json" \'
|
||||||
|
echo " -d '{\"consent_version_hash\":\"deadbeef\",\"consent_collection_method\":\"electronic_signature\",\"operator_of_record\":\"smoke\"}' | jq"
|
||||||
|
echo
|
||||||
|
echo ' Expected: HTTP 400 + error="consent_version_unknown"'
|
||||||
Loading…
x
Reference in New Issue
Block a user