Skip to content

ICA PAT Onboarding Guide

This guide walks through provisioning an enterprise service-account and issuing a smk_... Personal Access Token (PAT) for use by ICA (IBM Code Assistant) clients consuming the SkillMeat SAM contract API.

Prerequisites

Requirement Check
Enterprise edition deployed (SKILLMEAT_EDITION=enterprise) skillmeat version
SKILLMEAT_ENTERPRISE_PAT_SECRET set on the target node ssh agentic-nuc printenv SKILLMEAT_ENTERPRISE_PAT_SECRET
Access to SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS config see §2 below
alembic current shows latest migration applied skillmeat db status

1. Understand the provisioning model

SkillMeat enterprise PAT authentication uses a config-backed service-account registry (skillmeat/api/auth/service_account_registry.py). There is no REST endpoint for PAT issuance in this release — provisioning is done by:

  1. Seeding the service-account user row into enterprise_users via the bootstrap seed script (or the enterprise admin UI once available).
  2. Adding the PAT mapping to SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS — a JSON environment variable read at API startup.

D3 decision (locked): No new PAT issuance endpoint in W4. The service_account_registry already resolves PAT→(user_id, tenant_id); provisioning is seeding and config concern only.


2. Step 1 — Seed the service-account user row

The enterprise bootstrap seed (scripts/seed/modules/enterprise_bootstrap.py) creates a seeder-service-account row (SEEDER_SERVICE_ACCOUNT_UUID = 00000000-0000-0000-0000-000000000001) under the demo tenant automatically.

For a custom ICA service account, run the bootstrap seed against the target database and pass a deterministic UUID for your service account:

# On the target node (or via SSH tunnel)
SKILLMEAT_EDITION=enterprise \
SKILLMEAT_DATABASE_URL=<DATABASE_URL> \
python -m scripts.seed.run \
  --profile minimal \
  --service-account-uuid <SERVICE_ACCOUNT_UUID> \
  --dry-run

Replace <SERVICE_ACCOUNT_UUID> with the UUID you want to assign to the ICA service account (must be a valid non-zero UUID — see §Gotchas below).

Remove --dry-run to apply.

To verify the row was created:

psql "<DATABASE_URL>" -c \
  "SELECT id, clerk_user_id, email, role FROM enterprise_users WHERE id = '<SERVICE_ACCOUNT_UUID>';"

3. Step 2 — Generate an smk_... PAT

SkillMeat enterprise PATs are HMAC-signed tokens with the prefix smk_. Generate one using the PAT utility:

python -c "
import hmac, hashlib, secrets, base64, os
secret = os.environ['SKILLMEAT_ENTERPRISE_PAT_SECRET']
rand = secrets.token_hex(32)
sig = hmac.new(secret.encode(), rand.encode(), hashlib.sha256).hexdigest()[:16]
print(f'smk_{rand[:8]}_{sig}')
"

Important: The first 8 characters after smk_ (rand[:8] above) become the PAT prefix used as the registry lookup key in SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS.

Record the output as <PAT> and the first 8 characters after smk_ as <PAT_PREFIX>.


4. Step 3 — Register the PAT mapping in config

Add an entry to SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS (JSON, new-shape format):

{
  "<PAT_PREFIX>": {
    "tenant_id": "<TENANT_UUID>",
    "user_id": "<SERVICE_ACCOUNT_UUID>"
  }
}

Where:

Placeholder Value
<PAT_PREFIX> First 8 chars after smk_ from your generated PAT
<TENANT_UUID> Your enterprise tenant UUID (demo: 00000000-0000-4000-a000-000000000001)
<SERVICE_ACCOUNT_UUID> UUID from Step 1

Set this on the target node:

# Append to .env or set in systemd unit Environment=
export SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS='{"<PAT_PREFIX>": {"tenant_id": "<TENANT_UUID>", "user_id": "<SERVICE_ACCOUNT_UUID>"}}'

Then restart the API service for the new config to take effect:

ssh agentic-nuc systemctl --user restart skillmeat-api

5. Step 4 — Initial verification

Confirm the PAT resolves correctly before the full SAM round-trip:

# Health check (no auth required)
curl -s http://<NUC_HOST>:8080/health | jq .

# Auth smoke test — should return 200 with user context
curl -s http://<NUC_HOST>:8080/api/v1/user-collections \
  -H "Authorization: Bearer <PAT>" | jq .

# Confirm the PAT maps to the right tenant
curl -s http://<NUC_HOST>:8080/api/v1/settings \
  -H "Authorization: Bearer <PAT>" | jq '{edition: .edition}'

Where <NUC_HOST> is the enterprise node address (LAN: 10.42.10.76, alias agentic-nuc).


6. SAM contract round-trip exercise (post-merge nuc step)

STATUS: Pending post-merge nuc deploy.

The commands below document the reproducible nuc exercise for Phase 5 (P5-T3). They require the nuc to be running ≥ 6cffd5621 with ent_095 migration applied. Execute these after the Wave 1 branch is merged and deployed to the nuc.

6.1 Publish a version via SAM

# POST /api/v1/sam/artifacts/{artifact_id}/versions
# Publishes a new version of an artifact through the SAM contract.
curl -s -X POST \
  http://<NUC_HOST>:8080/api/v1/sam/artifacts/<ARTIFACT_ID>/versions \
  -H "Authorization: Bearer <PAT>" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "<VERSION_STRING>",
    "content_hash": "<CONTENT_HASH>",
    "changelog": "ICA onboarding smoke test"
  }' | jq .

# Expected: HTTP 201 with version_id in response
# Record the returned version_id as <VERSION_ID>

6.2 Read the manifest

# GET /api/v1/sam/artifacts/{artifact_id}/manifest
curl -s \
  http://<NUC_HOST>:8080/api/v1/sam/artifacts/<ARTIFACT_ID>/manifest \
  -H "Authorization: Bearer <PAT>" | jq .

# Expected: HTTP 200 with artifact manifest including latest version details

6.3 Submit → approve → promote state machine

# Submit for approval (none → pending)
curl -s -X POST \
  http://<NUC_HOST>:8080/api/v1/sam/artifacts/<ARTIFACT_ID>/submit \
  -H "Authorization: Bearer <PAT>" | jq .

# Approve (pending → approved)
curl -s -X POST \
  http://<NUC_HOST>:8080/api/v1/sam/artifacts/<ARTIFACT_ID>/approve \
  -H "Authorization: Bearer <PAT>" | jq .

# Gate-check promotion (returns 200 if approved)
curl -s -X POST \
  http://<NUC_HOST>:8080/api/v1/sam/artifacts/<ARTIFACT_ID>/promote \
  -H "Authorization: Bearer <PAT>" | jq .

Document the concrete artifact IDs, version IDs, and response payloads in .claude/worknotes/registry-core-w4/e2e-staging-result.md after completion.


D7 Deferral — Team-level PAT scoping

D7-DEFERRED (registry-core-w4/P4): Enterprise PAT tokens are issued at tenant scope only for this release. Team-level PAT scoping (assigning a team_id to a service account's PAT so it inherits team-level access controls) is not supported in MVP staging E2E.

When team-scoped service accounts are required, extend the SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS config shape to include an optional team_id field, and update skillmeat/api/auth/enterprise_pat_provider.py (the validate() return value) to populate AuthContext.team_id from the registry entry.

See the D7-DEFERRED comment at skillmeat/api/auth/enterprise_pat_provider.py around the team_id=None assignment for the exact deferral note.


Gotchas

All-zeros tenant UUID footgun

Never use 00000000-0000-0000-0000-000000000000 as tenant_id in the SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS config.

The correct demo tenant UUID is 00000000-0000-4000-a000-000000000001 (note the 4000 version segment and a000 variant segment). Using the all-zeros UUID causes PAT auth to fail silently — the registry will resolve the entry but the tenant context will be phantom, causing downstream 403s or empty result sets.

The enterprise seed (skillmeat/cache/seeds/enterprise_seed.py) includes a guard that raises ValueError if DEMO_TENANT_UUID resolves to all-zeros before any DB writes, which surfaces this misconfiguration at seed time rather than request time.

PAT secret rotation

Rotating SKILLMEAT_ENTERPRISE_PAT_SECRET invalidates all existing PATs because PAT validation is HMAC-based. Reissue all service-account PATs and update SKILLMEAT_ENTERPRISE_SERVICE_ACCOUNTS with new prefixes after rotation.

Legacy config shape rejected

The old {tenant_uuid: user_uuid} config shape is detected at startup and silently rejected (entries excluded from the PAT index, logged as ERROR). If PAT auth fails with 401 for a seemingly valid token, check the API startup logs for LEGACY_ENTERPRISE_SERVICE_ACCOUNTS_SHAPE messages.

API port

The SkillMeat API binds on port 8080, not 8000. All curl commands in this guide use :8080.


Reference

Resource Path
Auth provider skillmeat/api/auth/enterprise_pat_provider.py
Service-account registry skillmeat/api/auth/service_account_registry.py
Bootstrap seed scripts/seed/modules/enterprise_bootstrap.py
Audit RBAC seed (guard) skillmeat/cache/seeds/enterprise_seed.py
SAM versions handler skillmeat/api/routers/sam_versions_handler.py
SAM contract handler skillmeat/api/routers/sam_contract_handler.py
Auth architecture .claude/context/key-context/auth-architecture.md
Authentication setup docs/user/guides/authentication-setup.md