Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3be453b
feat(team-server): scaffold + self-managing schema (Phase 1)
Knapp-Kevin May 2, 2026
84fc288
feat(team-server): Slack OAuth + workspace allow-list (Phase 2)
Knapp-Kevin May 2, 2026
9504387
feat(team-server): Slack worker + canonical-extraction cache (Phase 3)
Knapp-Kevin May 2, 2026
c5e09c3
feat(team-server): HTTP /events API + materializer extension (Phase 4)
Knapp-Kevin May 2, 2026
dcd6f46
docs(governance): Priority C v0 plan/research/audit/seal artifacts
Knapp-Kevin May 2, 2026
0180e30
refactor(team-server): cache-contract migration to upsert-per-source_…
Knapp-Kevin May 2, 2026
661e870
feat(team-server): worker-task lifecycle pattern + Slack reference wi…
Knapp-Kevin May 2, 2026
863c5b6
feat(team-server): Notion API client + property serializer (Phase 1)
Knapp-Kevin May 2, 2026
9ce47eb
feat(team-server): Notion ingest worker + per-database watermark (Pha…
Knapp-Kevin May 2, 2026
1365cde
feat(team-server): Notion task registration on lifespan (Phase 3)
Knapp-Kevin May 2, 2026
601dc8d
docs(governance): Priority C v1 plan/audit/seal artifacts
Knapp-Kevin May 2, 2026
484bb88
refactor(team-server): cache contract gets classifier_version axis (P…
Knapp-Kevin May 2, 2026
0f3ca92
feat(team-server): heuristic classifier — pure deterministic Stage 1 …
Knapp-Kevin May 2, 2026
ad6437f
feat(team-server): trigger rules schema + per-channel/db merge (Phase 2)
Knapp-Kevin May 2, 2026
bcdbb49
feat(team-server): real LLM extractor via Anthropic SDK (Phase 3)
Knapp-Kevin May 2, 2026
9f2b869
feat(team-server): pipeline integration — workers route Stage 1 → Sta…
Knapp-Kevin May 2, 2026
0d3af33
feat(team-server): corpus learner — option-c feedback loop (Phase 5)
Knapp-Kevin May 2, 2026
2863fbe
docs(governance): Priority C v1.1 plan/audit/seal artifacts
Knapp-Kevin May 2, 2026
b54fde3
feat(team-server): channel_allowlist startup-time YAML sync (closes #…
Knapp-Kevin May 3, 2026
8e9c2f5
feat(team-server): periodic team-server event consumer + payload brid…
Knapp-Kevin May 3, 2026
38ca001
feat(team-server): materializer dispatch case for team-server JSONL e…
Knapp-Kevin May 3, 2026
238c0ce
docs(governance): v0 release-blockers plan/audit/seal artifacts
Knapp-Kevin May 3, 2026
8f97151
feat(skills): preflight Step 5.6 — capture refinements on contradiction
Knapp-Kevin May 3, 2026
76719e5
feat(events): SessionEnd transcript bridge — propagate parent transcr…
Knapp-Kevin May 3, 2026
3c59a41
docs(governance): Priority B v0 final-blockers plan/audit/seal artifacts
Knapp-Kevin May 3, 2026
a03aebe
style: ruff check --fix + ruff format (CI lint pass)
Knapp-Kevin May 3, 2026
f37bd0b
fix(team-server): satisfy mypy on llm_extractor + app.py
Knapp-Kevin May 4, 2026
9894338
ci: re-trigger workflows on f37bd0b after PR #159 closure interrupted…
Knapp-Kevin May 4, 2026
5e26949
ci: docstring tweak to force pull_request:synchronize re-trigger
Knapp-Kevin May 4, 2026
5b0cd30
test(manual-qa): Slack OAuth E2E harness for PR #153 unchecked items
May 5, 2026
3d11046
merge: dev into claude/priority-c-selective-ingest
May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"hooks": [
{
"type": "command",
"command": "[ -d .bicameral ] && [ -z \"$BICAMERAL_SESSION_END_RUNNING\" ] && BICAMERAL_SESSION_END_RUNNING=1 claude -p '/bicameral-capture-corrections --auto-ingest' || true"
"command": "python3 -m events.session_end_bridge"
}
]
}
Expand Down
463 changes: 0 additions & 463 deletions .claude/skills/bicameral-preflight/SKILL.md

This file was deleted.

163 changes: 163 additions & 0 deletions .github/workflows/slack-oauth-manual-qa.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: slack oauth manual qa (PR #153)

# Manual QA harness for the two unchecked items in PR #153's test plan:
# - docker-compose stack health
# - Slack OAuth round-trip in a dev workspace
#
# workflow_dispatch ONLY — never runs on push/PR. Gated by the existing
# `recording-approval` environment so the run sits in "Waiting" until a
# maintainer with reviewer permission clicks Approve. Same gate the
# v0-user-flow-e2e recording job uses.
#
# Required secrets (repo or env-scoped to `recording-approval`):
# SLACK_CLIENT_ID - dev Slack OAuth app
# SLACK_CLIENT_SECRET - dev Slack OAuth app
# SLACK_STORAGE_STATE_B64 - base64 of Playwright storage_state.json for a
# pre-logged-in test Slack user
# See tests/manual_qa/README.md for capture steps.

on:
workflow_dispatch:

jobs:
slack-oauth-e2e:
name: slack oauth round-trip (manual approval)
runs-on: ubuntu-latest
environment: recording-approval
timeout-minutes: 25

steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install test deps + Playwright Chromium
run: |
pip install pytest httpx playwright cryptography
playwright install --with-deps chromium

- name: Required-secret presence probe
env:
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
SLACK_STORAGE_STATE_B64: ${{ secrets.SLACK_STORAGE_STATE_B64 }}
run: |
set -e
for v in SLACK_CLIENT_ID SLACK_CLIENT_SECRET SLACK_STORAGE_STATE_B64; do
if [ -z "${!v}" ]; then
echo "::error::secret $v is missing on the recording-approval environment"
exit 1
fi
done

- name: Install cloudflared
run: |
curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o /tmp/cloudflared.deb
sudo dpkg -i /tmp/cloudflared.deb

- name: Start cloudflared quick tunnel; capture URL
# Boots a tunnel pointed at localhost:8765 (where docker-compose
# will listen). Cloudflare emits the trycloudflare.com URL to
# stderr/stdout once the tunnel registers; we tail that and
# export it as MANUAL_QA_PUBLIC_URL for downstream steps.
run: |
mkdir -p /tmp/tunnel
nohup cloudflared tunnel --no-autoupdate --url http://localhost:8765 \
> /tmp/tunnel/log 2>&1 &
echo $! > /tmp/tunnel/pid
for i in $(seq 1 30); do
url=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /tmp/tunnel/log | head -1 || true)
if [ -n "$url" ]; then
echo "MANUAL_QA_PUBLIC_URL=$url" >> "$GITHUB_ENV"
echo "Tunnel up at $url"
exit 0
fi
sleep 2
done
echo "::error::cloudflared tunnel did not advertise a URL within 60s"
cat /tmp/tunnel/log
exit 1

- name: Generate Fernet key for token-at-rest encryption
run: |
key=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
echo "::add-mask::$key"
echo "BICAMERAL_TEAM_SERVER_SECRET_KEY=$key" >> "$GITHUB_ENV"

- name: Start team-server stack
env:
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
run: |
export SLACK_REDIRECT_URI="${MANUAL_QA_PUBLIC_URL}/oauth/slack/callback"
docker compose \
-f deploy/team-server.docker-compose.yml \
-f tests/manual_qa/docker-compose.override.yml \
up -d --build

- name: Wait for /health (via local port)
run: |
for i in $(seq 1 60); do
if curl -fsS http://localhost:8765/health >/dev/null 2>&1; then
echo "team-server healthy"
exit 0
fi
sleep 2
done
echo "::error::team-server /health never became OK"
docker compose -f deploy/team-server.docker-compose.yml logs --tail=200
exit 1

- name: Run manual-QA suite
env:
SLACK_STORAGE_STATE_B64: ${{ secrets.SLACK_STORAGE_STATE_B64 }}
run: |
mkdir -p test-results/manual-qa
pytest tests/manual_qa/ -v -s \
--junit-xml=test-results/manual-qa/junit.xml \
--tb=short \
--capture=no
# Continue on test failure so we still capture videos + logs.
continue-on-error: true
id: pytest

- name: Collect Playwright videos
if: always()
run: |
mkdir -p artifacts/videos
# pytest tmp_path roots vary by runner; sweep both common locations.
find /tmp /home -name "*.webm" -path "*manual-qa*" -o -name "*.webm" -path "*pytest*" 2>/dev/null \
| xargs -I {} cp -v {} artifacts/videos/ 2>/dev/null || true
ls -la artifacts/videos/ || true

- name: Capture team-server logs
if: always()
run: |
mkdir -p artifacts/logs
docker compose -f deploy/team-server.docker-compose.yml \
logs --no-color > artifacts/logs/team-server.log 2>&1 || true
cp /tmp/tunnel/log artifacts/logs/cloudflared.log 2>/dev/null || true

- name: Upload manual-QA evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: pr153-slack-oauth-evidence
path: |
artifacts/
test-results/manual-qa/
retention-days: 30
if-no-files-found: warn

- name: Tear down stack
if: always()
run: |
docker compose -f deploy/team-server.docker-compose.yml down -v || true
if [ -f /tmp/tunnel/pid ]; then kill "$(cat /tmp/tunnel/pid)" 2>/dev/null || true; fi

- name: Re-raise pytest failure
if: steps.pytest.outcome == 'failure'
run: exit 1
45 changes: 45 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,48 @@ From eng review 2026-04-26. Four independent workstreams — A+B+C launch in par
All mocks deleted. V1 introduces no new mocks (read-path advisory
only). See git history for the original Phase 1 / Phase 2 mock
replacements (`RealCodeLocatorAdapter`, `SurrealDBLedgerAdapter`).

---

## Priority C v1 — Notion ingest + cache contract migration (2026-05-02)

Plan: [`plan-priority-c-team-server-notion-v1.md`](plan-priority-c-team-server-notion-v1.md). Three-round
audit cycle (VETO → VETO → PASS); implementation 64/64 team-server tests passing.

### Phase 0: Cache contract migration — DONE

- [x] `team_server/schema.py` — schema v1→v2; `schema_version` table; `_MIGRATIONS` callable dispatch
- [x] `team_server/extraction/canonical_cache.py` — `get_or_compute` replaced by `upsert_canonical_extraction(...) -> tuple[dict, bool]`
- [x] `team_server/workers/slack_worker.py` — adapted to new tuple-return contract; `_cache_row_exists` deleted
- [x] `tests/test_team_server_cache_upsert.py` — 4 functionality tests
- [x] `tests/test_team_server_schema_migration.py` — 4 functionality tests (incl. callable-dispatch + schema_version row)
- [x] `tests/test_team_server_slack_worker.py` — adapted; new no-event-on-unchanged + event-on-changed pair
- [x] `tests/test_team_server_canonical_cache.py` — rewritten under v2 upsert contract

### Phase 0.5: Worker-task lifecycle pattern + Slack reference wiring — DONE

Closes the v0 dormant-Slack-worker gap (v0 plan claimed an active worker; v0 code shipped a function with no production caller).

- [x] `team_server/workers/runner.py` — `worker_loop(name, interval, work_fn)` lifecycle helper
- [x] `team_server/workers/slack_runner.py` — `run_slack_iteration(db_client, extractor)` with workspace iteration, Fernet decryption, channel allowlist read, per-workspace failure isolation
- [x] `team_server/app.py` — lifespan registers Slack task unconditionally + Notion task opt-in
- [x] `tests/test_team_server_worker_lifecycle.py` — 7 functionality tests (incl. round-trip encryption test closing audit-round-2 blind spot)

### Phase 1: Notion auth + content fetch primitives — DONE

- [x] `team_server/auth/notion_client.py` — `load_token`, `list_databases`, `query_database`, `fetch_page_blocks`; `Notion-Version: 2022-06-28` pinned
- [x] `team_server/extraction/notion_serializer.py` — `serialize_row(page, blocks) -> str` deterministic
- [x] `team_server/config.py` — `DEFAULT_CONFIG_PATH` constant with env-var fallback
- [x] `tests/test_team_server_notion_client.py` — 7 functionality tests
- [x] `tests/test_team_server_notion_serializer.py` — 3 functionality tests

### Phase 2: Notion ingest worker — DONE

- [x] `team_server/workers/notion_worker.py` — polls allowlist-via-share databases, per-database watermark, peer-author event identity
- [x] `tests/test_team_server_notion_worker.py` — 9 functionality tests (incl. partial-failure recovery, edit semantics, content_hash via deterministic serialization)

### Phase 3: Notion task registration — DONE

- [x] `team_server/workers/notion_runner.py` — `run_notion_iteration(db_client, token, extractor)` thin wrapper for symmetry with slack_runner
- [x] `team_server/app.py` — Notion task registration via the same `worker_loop` helper; opt-in on `notion_client.load_token` success
- [x] `tests/test_team_server_notion_lifecycle.py` — 4 functionality tests
28 changes: 28 additions & 0 deletions deploy/Dockerfile.team-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM python:3.11-slim

WORKDIR /app

# Install system deps for cryptography/build
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libffi-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*

# Copy team-server requirements + install
COPY team_server/requirements.txt /app/team_server/requirements.txt
RUN pip install --no-cache-dir -r /app/team_server/requirements.txt

# Copy the team_server package + its runtime deps from the bicameral-mcp repo
COPY team_server /app/team_server
COPY ledger /app/ledger
COPY events /app/events
COPY contracts.py /app/contracts.py

# Run as a non-root user for the standard self-managing-backend hygiene
RUN useradd --create-home --shell /bin/bash teamserver
USER teamserver

EXPOSE 8765

CMD ["uvicorn", "team_server.app:create_app", "--factory", "--host", "0.0.0.0", "--port", "8765"]
23 changes: 23 additions & 0 deletions deploy/team-server.docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
services:
bicameral-team-server:
build:
context: ..
dockerfile: deploy/Dockerfile.team-server
image: bicameral-team-server:dev
ports:
- "${TEAM_SERVER_PORT:-8765}:8765"
environment:
BICAMERAL_TEAM_SERVER_SURREAL_URL: "surrealkv:///data/team-server.db"
BICAMERAL_TEAM_SERVER_SECRET_KEY: "${BICAMERAL_TEAM_SERVER_SECRET_KEY:?secret-key required}"
volumes:
- team-server-data:/data
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8765/health').read()"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped

volumes:
team-server-data:
Loading
Loading