Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
35fd24c
test(identity): define durable authentication-age provenance
seonghobae Aug 9, 2026
c7862d2
feat(identity): preserve authentication instant in session lifecycle
seonghobae Aug 9, 2026
77bfb03
feat(identity): expose credential-free authentication age
seonghobae Aug 9, 2026
8b76e09
feat(identity): persist session authentication provenance
seonghobae Aug 9, 2026
511bc41
feat(identity): migrate session authentication age
seonghobae Aug 9, 2026
ef7457e
test(identity): cover persisted authentication age
seonghobae Aug 9, 2026
acf8f1a
test(identity): prove authentication age survives PostgreSQL rotation
seonghobae Aug 9, 2026
2ad45a9
fix(readiness): separate canonical buyer gaps from capability maturit…
seonghobae Aug 9, 2026
38dde3f
fix(agent): verify explicit model catalog and Compose runtime (#133)
seonghobae Aug 9, 2026
e957e2e
fix(identity): preserve legacy session authentication lineage
seonghobae Aug 9, 2026
5ba7093
test(identity): prove legacy authentication-age migration
seonghobae Aug 9, 2026
211fb0f
fix(identity): narrow callback session dependency
seonghobae Aug 9, 2026
45a6b1d
Merge 211fb0f7a61934ed397317a491e2f6f8ee62b740 into 38dde3fa44f6ad24d…
seonghobae Aug 9, 2026
8394050
test(identity): isolate migration regression as test code
seonghobae Aug 9, 2026
5e8676a
test(identity): move migration regression under tests
seonghobae Aug 9, 2026
ad9c400
test(readiness): reject malformed buyer-gap evidence
seonghobae Aug 9, 2026
c271ecf
docs(readiness): explain buyer-gap renderer contracts
seonghobae Aug 9, 2026
acea585
test(identity): require staged authentication-age validation
seonghobae Aug 9, 2026
0cba1a1
fix(identity): stage authentication-age constraints before validation
seonghobae Aug 9, 2026
6b70645
fix(identity): finalize validated authentication-age constraint
seonghobae Aug 9, 2026
0c201a8
test(identity): verify staged authentication-age finalization
seonghobae Aug 9, 2026
6ad6392
fix(readiness): validate buyer-gap evidence boundary
seonghobae Aug 9, 2026
5bce030
test(identity): reject cross-boundary rotation lineage
seonghobae Aug 9, 2026
53be795
test(identity): align invalid lineage cases with existing tenant FK
seonghobae Aug 9, 2026
bb56801
test(identity): keep migration policy regressions out of app-code scan
seonghobae Aug 9, 2026
e384333
test(identity): remove migration contract from production source
seonghobae Aug 9, 2026
0d836c9
test(identity): remove dynamic SQL from migration fixture
seonghobae Aug 9, 2026
696c1a5
test(identity): clarify staged authentication migration assertions
seonghobae Aug 9, 2026
187bf99
test(identity): normalize migration layout before assertions
seonghobae Aug 9, 2026
56c396f
test(agent): resolve installed OpenCode through pnpm exec
seonghobae Aug 9, 2026
773ba8e
test(identity): use neutral disposable database name
seonghobae Aug 9, 2026
d44ae8e
test(identity): serialize migration fixture database
seonghobae Aug 9, 2026
831d42f
test(readiness): reject malformed buyer gap items
seonghobae Aug 9, 2026
174b409
chore(agent): restore protected-main workflow contracts
seonghobae Aug 9, 2026
e2ae810
fix(readiness): validate attached buyer gap evidence
seonghobae Aug 9, 2026
b3da6ce
test(identity): always release migration fixture lock
seonghobae Aug 9, 2026
5a2f400
docs(readiness): document buyer gap validation helpers
seonghobae Aug 9, 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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,41 @@ concurrency:
cancel-in-progress: true

jobs:
compose_runtime:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false

- name: Start and probe Compose infrastructure
shell: bash
run: |
set -Eeuo pipefail
cleanup() {
status=$?
trap - EXIT
if [ "$status" -ne 0 ]; then
docker compose ps --all || true
docker compose logs --no-color --timestamps --tail 200 postgres nats || true
fi
docker compose down --volumes --remove-orphans || true
exit "$status"
}
trap cleanup EXIT

docker compose up --detach --wait --wait-timeout 90
docker compose exec --no-TTY postgres psql -U lifeos -d lifeos -v ON_ERROR_STOP=1 -tAc 'SELECT 1' |
grep -Fx 1
curl --fail --silent --show-error --max-time 5 \
http://127.0.0.1:8222/jsz |
jq -e '(.streams | type) == "number" and (.consumers | type) == "number"' \
>/dev/null

validate:
needs: compose_runtime
runs-on: ubuntu-latest
timeout-minutes: 20
env:
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/commercial-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
Expand All @@ -51,15 +52,19 @@ jobs:
node packages/commercial-readiness/src/cli.mjs snapshot \
--repository "$GITHUB_REPOSITORY" \
--policy product/commercial-readiness-policy.json \
--commit "$GITHUB_SHA" \
--commit "${{ github.event.pull_request.head.sha || github.sha }}" \
--generated-at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--output "$EVIDENCE_DIR/github-snapshot.json"

- name: Audit product capabilities and buyer gaps
- name: Audit capability maturity and canonical buyer gaps
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
node packages/commercial-readiness/src/cli.mjs audit \
node packages/commercial-readiness/src/buyer-gap-cli.mjs \
--repository "$GITHUB_REPOSITORY" \
--manifest product/capabilities.json \
--buyer-gaps product/buyer-gaps.json \
--snapshot "$EVIDENCE_DIR/github-snapshot.json" \
--policy product/commercial-readiness-policy.json \
--root . \
Expand Down
100 changes: 86 additions & 14 deletions .github/workflows/opencode-commercial-development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -242,20 +242,44 @@ jobs:
COREPACK_HOME="$trusted_corepack_home" \
corepack install --global "$package_manager"
chmod -R u=rwX,go=rX "$trusted_corepack_home"
MODEL_HOME="$model_home" NIM_BRIDGE_PORT="$NIM_BRIDGE_PORT" python3 - <<'PYCONFIG'
MODEL_HOME="$model_home" MODEL_WORKSPACE="$model_workspace" NIM_BRIDGE_PORT="$NIM_BRIDGE_PORT" RECEIPT_DIR="$RECEIPT_DIR" python3 - <<'PYCONFIG'
import json
import os
from pathlib import Path

run = json.loads(
(Path(os.environ['RECEIPT_DIR']) / 'run.json').read_text(encoding='utf-8')
)
model_label = run['model_label']
provider_id, separator, model_id = model_label.partition('/')
if provider_id != 'nvidia' or separator != '/' or not model_id:
raise SystemExit('invalid_nvidia_model_label')
model_workspace_path = Path(os.environ['MODEL_WORKSPACE'])
instruction_files = [
model_workspace_path / 'AGENTS.md',
model_workspace_path / 'CLAUDE.md',
]
if any(not path.is_file() for path in instruction_files):
raise SystemExit('missing_reviewed_model_instruction')
instruction_paths = [str(path) for path in instruction_files]

config = {
'$schema': 'https://opencode.ai/config.json',
'autoupdate': False,
'share': 'disabled',
'enabled_providers': ['nvidia'],
'model': model_label,
'small_model': model_label,
'instructions': instruction_paths,
'provider': {
'nvidia': {
'whitelist': [model_id],
'models': {model_id: {'name': model_id}},
'options': {
'baseURL': f"http://127.0.0.1:{os.environ['NIM_BRIDGE_PORT']}/v1",
'apiKey': 'local-loopback-placeholder',
'timeout': 60_000,
'chunkTimeout': 30_000,
},
},
},
Expand Down Expand Up @@ -305,9 +329,28 @@ jobs:
echo 'MODEL_NETWORK_PHASE=model'
} >> "$GITHUB_ENV"

- name: Validate the explicit OpenCode model catalog
id: model_catalog
if: steps.branch.outcome == 'success'
run: |
set -Eeuo pipefail
OPENCODE_MODEL="$(jq -r '.model_label' "$RECEIPT_DIR/run.json")"
sudo -u opencode_model env -i \
HOME="$MODEL_HOME" \
PATH="$PATH" \
COREPACK_HOME="$TRUSTED_COREPACK_HOME" \
COREPACK_ENABLE_NETWORK=0 \
OPENCODE_CONFIG="$MODEL_HOME/opencode.json" \
OPENCODE_DISABLE_AUTOUPDATE=true \
OPENCODE_DISABLE_MODELS_FETCH=true \
OPENCODE_DISABLE_PROJECT_CONFIG=true \
NVIDIA_API_KEY=local-loopback-placeholder \
bash -c 'cd "$1" && catalog="$(pnpm --filter @life-os/commercial-development-agent exec opencode models nvidia)" && test "$catalog" = "$2"' \
_ "$MODEL_WORKSPACE" "$OPENCODE_MODEL"

- name: Start loopback NVIDIA credential bridge
id: bridge
if: steps.branch.outcome == 'success'
if: steps.model_catalog.outcome == 'success'
env:
NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
run: |
Expand Down Expand Up @@ -451,6 +494,8 @@ jobs:
COREPACK_ENABLE_NETWORK=0 \
OPENCODE_CONFIG="$MODEL_HOME/opencode.json" \
OPENCODE_DISABLE_AUTOUPDATE=true \
OPENCODE_DISABLE_MODELS_FETCH=true \
OPENCODE_DISABLE_PROJECT_CONFIG=true \
NVIDIA_API_KEY=local-loopback-placeholder \
bash -c 'cd "$1" && timeout --signal=TERM --kill-after=30s 90m pnpm --filter @life-os/commercial-development-agent exec opencode run --pure --auto --model "$2" --format json --file .opencode-task.md "Execute the attached policy-isolated LifeOS task."' \
_ "$MODEL_WORKSPACE" "$OPENCODE_MODEL" \
Expand Down Expand Up @@ -692,7 +737,8 @@ jobs:
id: verification
if: steps.diff.outputs.accepted == 'true'
run: |
set +e
set -Eeuo pipefail
status=0
sudo -u opencode_model env -i \
HOME="$MODEL_HOME" \
PATH="$PATH" \
Expand All @@ -705,21 +751,37 @@ jobs:
HABIT_DATABASE_URL="$HABIT_DATABASE_URL" \
NOTIFICATION_DATABASE_URL="$NOTIFICATION_DATABASE_URL" \
PRIVACY_DATABASE_URL="$PRIVACY_DATABASE_URL" \
bash -c 'cd "$1" && pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build && docker compose config >/dev/null' \
bash -c 'cd "$1" && pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build' \
_ "$MODEL_WORKSPACE" \
> "$RECEIPT_DIR/verification.log" 2>&1
status=$?
> "$RECEIPT_DIR/verification.log" 2>&1 || status=$?
sudo pkill --signal TERM --euid opencode_model 2>/dev/null || true
set -e
chmod 0600 "$RECEIPT_DIR/verification.log"
if [ "$status" -eq 0 ]; then
echo 'passed=true' >> "$GITHUB_OUTPUT"
else
echo 'passed=false' >> "$GITHUB_OUTPUT"
fi

- name: Materialize verified candidate through trusted boundary
- name: Validate Compose configuration through trusted boundary
id: compose_config
if: steps.verification.outputs.passed == 'true'
run: |
set -Eeuo pipefail
status=0
docker compose \
--file "$MODEL_WORKSPACE/compose.yaml" \
--project-directory "$MODEL_WORKSPACE" \
config --quiet \
> "$RECEIPT_DIR/compose-validation.log" 2>&1 || status=$?
chmod 0600 "$RECEIPT_DIR/compose-validation.log"
if [ "$status" -eq 0 ]; then
echo 'passed=true' >> "$GITHUB_OUTPUT"
else
echo 'passed=false' >> "$GITHUB_OUTPUT"
fi

- name: Materialize verified candidate through trusted boundary
if: steps.compose_config.outputs.passed == 'true'
run: |
set -Eeuo pipefail
python3 - <<'PYMATERIALIZE'
Expand Down Expand Up @@ -801,7 +863,7 @@ jobs:

- name: Recheck the exact main base before remote mutation
id: base
if: steps.verification.outputs.passed == 'true'
if: steps.compose_config.outputs.passed == 'true'
run: |
set -Eeuo pipefail
current="$(timeout 30s git ls-remote origin refs/heads/main | awk 'NR == 1 {print $1}')"
Expand Down Expand Up @@ -876,10 +938,12 @@ jobs:
env:
OPEN_PULL_REQUESTS: ${{ steps.github_evidence.outputs.open_pull_requests }}
SELECTED: ${{ steps.selection.outputs.selected }}
MODEL_CATALOG_OUTCOME: ${{ steps.model_catalog.outcome }}
BRIDGE_REASON: ${{ steps.bridge.outputs.reason }}
MODEL_REASON: ${{ steps.model.outputs.reason }}
DIFF_ACCEPTED: ${{ steps.diff.outputs.accepted }}
VERIFICATION_PASSED: ${{ steps.verification.outputs.passed }}
COMPOSE_CONFIG_PASSED: ${{ steps.compose_config.outputs.passed }}
BASE_MATCHED: ${{ steps.base.outputs.matched }}
PR_CREATED: ${{ steps.mutation.outputs.created }}
BRANCH_NAME: ${{ steps.branch.outputs.branch_name }}
Expand Down Expand Up @@ -916,15 +980,20 @@ jobs:

open_prs = os.environ.get('OPEN_PULL_REQUESTS', '0')
selected = os.environ.get('SELECTED', 'false') == 'true'
model_catalog_outcome = os.environ.get('MODEL_CATALOG_OUTCOME', '')
bridge_reason = os.environ.get('BRIDGE_REASON', '')
model_reason = os.environ.get('MODEL_REASON', '')
diff_accepted = os.environ.get('DIFF_ACCEPTED', '') == 'true'
verification = os.environ.get('VERIFICATION_PASSED', '') == 'true'
source_verification = os.environ.get('VERIFICATION_PASSED', '') == 'true'
compose_configuration = os.environ.get('COMPOSE_CONFIG_PASSED', '') == 'true'
verification = source_verification and compose_configuration
base_matched = os.environ.get('BASE_MATCHED', '') == 'true'
pr_created = os.environ.get('PR_CREATED', '') == 'true'

if open_prs != '0' or not selected:
status, reason = 'unavailable', 'no_eligible_issue'
elif model_catalog_outcome != 'success':
status, reason = 'failed', 'invalid_configuration'
elif bridge_reason == 'provider_credential_missing':
status, reason = 'unavailable', 'provider_credential_missing'
elif bridge_reason != 'completed' or model_reason != 'completed':
Expand All @@ -943,10 +1012,12 @@ jobs:
validations = [
{'name': 'open_pull_request_drain', 'status': 'passed' if open_prs == '0' else 'failed'},
{'name': 'issue_policy', 'status': 'passed' if selected else 'skipped'},
{'name': 'credential_bridge', 'status': 'skipped' if not selected else 'passed' if bridge_reason == 'completed' else 'failed'},
{'name': 'provider_run', 'status': 'skipped' if not selected or bridge_reason != 'completed' else 'passed' if model_reason == 'completed' else 'failed'},
{'name': 'diff_policy', 'status': 'passed' if diff_accepted else 'skipped' if not selected else 'failed'},
{'name': 'repository_verification', 'status': 'passed' if verification else 'skipped' if not diff_accepted else 'failed'},
{'name': 'model_catalog', 'status': 'skipped' if not selected or open_prs != '0' else 'passed' if model_catalog_outcome == 'success' else 'failed'},
{'name': 'credential_bridge', 'status': 'skipped' if not selected or open_prs != '0' or model_catalog_outcome != 'success' else 'passed' if bridge_reason == 'completed' else 'failed'},
{'name': 'provider_run', 'status': 'skipped' if not selected or open_prs != '0' or model_catalog_outcome != 'success' or bridge_reason != 'completed' else 'passed' if model_reason == 'completed' else 'failed'},
{'name': 'diff_policy', 'status': 'skipped' if not selected or open_prs != '0' or model_catalog_outcome != 'success' or bridge_reason != 'completed' or model_reason != 'completed' else 'passed' if diff_accepted else 'failed'},
{'name': 'repository_verification', 'status': 'passed' if source_verification else 'skipped' if not diff_accepted else 'failed'},
{'name': 'compose_configuration', 'status': 'passed' if compose_configuration else 'skipped' if not source_verification else 'failed'},
{'name': 'base_sha', 'status': 'passed' if base_matched else 'skipped' if not verification else 'failed'},
]
input_value = {
Expand Down Expand Up @@ -1012,6 +1083,7 @@ jobs:
"$RECEIPT_DIR/prompt.txt" \
"$RECEIPT_DIR/opencode.log" \
"$RECEIPT_DIR/verification.log" \
"$RECEIPT_DIR/compose-validation.log" \
"$RECEIPT_DIR/nim-bridge.py" \
"$RECEIPT_DIR/issues.json" \
"$RECEIPT_DIR/pulls.json" \
Expand Down
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ Pull requests follow one loop: inspect every review and check, fix root causes,

Scheduled model-assisted automation uses `NVIDIA_NIM_API_KEY`; `COPILOT_GITHUB_TOKEN` is prohibited. Existing dedicated review-agent credentials are not repurposed. Deterministic audit and merge eligibility remain independently enforceable even when a model provider is unavailable.

The pinned OpenCode configuration disables project-local overrides, explicitly reloads reviewed repository instructions, enables only NVIDIA, registers and whitelists one model label independently of the bundled catalog, pins primary and small-model work to it, and checks that effective catalog offline before its credential bridge starts; the bridge exposes no provider-wide discovery route. Model-generated source verification runs without Docker authority. A later trusted operation parses the accepted candidate's explicitly selected Compose file, while credential-free pull-request CI starts digest-pinned images, proves PostgreSQL query execution and NATS JetStream availability, binds published ports to loopback, and tears down unconditionally.

## 6. Documentation hierarchy

1. `AGENTS.md` — repository-wide agent and merge rules.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to LifeOS are documented in this file.

### Fixed

- The OpenCode development loop now prevents project settings from overriding its pinned offline NVIDIA model, records catalog failures accurately, parses the accepted candidate's exact Compose file outside the model account, and requires digest-pinned PostgreSQL queries plus NATS JetStream probes in pull-request CI.
- Live contextual-orchestrator responses now classify successful empty bodies as evaluation failures, emit exactly one terminal observation, canonicalize retained timestamps safely, and preserve null metric denominators instead of fabricating deltas.
- Stale AI proposal revision conflicts now belong to the technology-independent audit domain while the PostgreSQL adapter preserves its compatibility export.
- Planning search now normalizes browser query text and prevents stale or unmounted requests from replacing the latest visible result state.
Expand All @@ -32,6 +33,7 @@ All notable changes to LifeOS are documented in this file.

### Security

- The commercial-development model account no longer performs Docker commands, never receives Docker-socket authority, and cannot trigger provider-wide model discovery through the credential bridge.
- The scheduled live-model harness uses only `NVIDIA_NIM_API_KEY`, seeds it through the encrypted contextual-orchestrator credential registry, installs hash-locked dependencies from an exact commit, confines LifeOS traffic to loopback, allowlists NVIDIA NIM egress, and excludes provider credentials, prompts, responses, traces, and hidden reasoning from retained artifacts.
- Proposal quality reports now discard nested model failures and response bodies, normalize labeled sentinel checks, expose no provider credential or mutation dependency, and measure prompt-injection resistance together with benign utility instead of rewarding blanket refusal.
- External proposal generation now accepts only one credential-free HTTPS orchestrator origin, stops responses at 65536 bytes, enforces a bounded abort timeout, supplies no tools, treats planning context as untrusted data, and exposes only sanitized failures.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
ALTER TABLE identity.sessions
ADD COLUMN authenticated_at timestamptz;

WITH RECURSIVE session_authentication_lineage AS (
SELECT
session_row.id,
session_row.user_id,
session_row.workspace_id,
session_row.created_at AS root_authenticated_at
FROM identity.sessions AS session_row
WHERE session_row.rotated_from_id IS NULL

UNION ALL

SELECT
child_session.id,
child_session.user_id,
child_session.workspace_id,
parent_session.root_authenticated_at
FROM identity.sessions AS child_session
JOIN session_authentication_lineage AS parent_session
ON child_session.rotated_from_id = parent_session.id
AND child_session.user_id = parent_session.user_id
AND child_session.workspace_id = parent_session.workspace_id
)
UPDATE identity.sessions AS session_row
SET authenticated_at = lineage.root_authenticated_at
FROM session_authentication_lineage AS lineage
WHERE session_row.id = lineage.id;

ALTER TABLE identity.sessions
ADD CONSTRAINT sessions_authentication_present
CHECK (authenticated_at IS NOT NULL) NOT VALID,
ADD CONSTRAINT sessions_authentication_not_after_creation
CHECK (authenticated_at <= created_at) NOT VALID;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ALTER TABLE identity.sessions
VALIDATE CONSTRAINT sessions_authentication_present;

ALTER TABLE identity.sessions
VALIDATE CONSTRAINT sessions_authentication_not_after_creation;

ALTER TABLE identity.sessions
ALTER COLUMN authenticated_at SET NOT NULL;

ALTER TABLE identity.sessions
DROP CONSTRAINT sessions_authentication_present;
Loading
Loading