Skip to content

Commit 90a129c

Browse files
authored
Merge pull request #1 from Keon-Systems/phase5-keon-sdk-v1
feat(sdk): phase5 - src/ layout restructure and v1.0.0 release prep
2 parents 072ddd6 + a5e17df commit 90a129c

15 files changed

Lines changed: 383 additions & 1 deletion

.gitignore

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*.pyo
5+
*.pyd
6+
.Python
7+
*.egg
8+
*.egg-info/
9+
dist/
10+
build/
11+
wheels/
12+
*.whl
13+
.eggs/
14+
15+
# Virtual environments
16+
.venv/
17+
venv/
18+
env/
19+
ENV/
20+
21+
# Testing
22+
.pytest_cache/
23+
.mypy_cache/
24+
.coverage
25+
coverage.xml
26+
htmlcov/
27+
.tox/
28+
nosetests.xml
29+
*.log
30+
31+
# Environment
32+
.env
33+
.env.*
34+
!.env.example
35+
36+
# IDE
37+
.vscode/
38+
.idea/
39+
*.iml
40+
41+
# OS
42+
.DS_Store
43+
Thumbs.db

RELEASE.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Keon SDK Python — Release Notes
2+
## v1.0.0 — 2026-02-22
3+
4+
---
5+
6+
## What Shipped
7+
8+
### `subjectHash` Required on `DecideRequest` (Phase 5)
9+
`DecideRequest` now requires `subjectHash` (alias: `subjectHash`):
10+
11+
```python
12+
DecideRequest(
13+
correlationId="t:my-tenant|c:<uuidv7>",
14+
tenantId="my-tenant",
15+
actorId="agent-1",
16+
action="EXECUTE_WORKFLOW",
17+
resourceType="workflow",
18+
resourceId="wf-123",
19+
subjectHash="<sha256 hex of canonical subject payload>",
20+
)
21+
```
22+
23+
**Empty or absent `subjectHash``ValidationError` at construction.** Fail-closed by design.
24+
25+
`subjectHash` MUST be the SHA-256 hex digest of the canonical (JCS) subject payload being governed. This binds the policy decision to the specific content being authorized.
26+
27+
### Schema Drift Tripwire Updated
28+
`_DECIDE_FIELDS_HASH` updated from `6a8c75b57593cd19``5c7ae3e6d532d49a`.
29+
30+
Any future unapproved change to `DecideRequest` required fields will trip CI.
31+
32+
---
33+
34+
## Previously Shipped (Sealed at v1.0.0)
35+
36+
- **KEON-SDK-SENTINEL-1a**: `correlationId` required; malformed → rejected; `tenantId` + `actorId` mandatory
37+
- **KEON-SDK-SENTINEL-1b**: `DecisionReceipt` schema strict; `receiptId` pattern `^dr-<uuidv7>`
38+
- **KEON-SDK-SENTINEL-1c**: Timeout → `NetworkError` (fail-closed); gateway never allows on failure
39+
40+
---
41+
42+
## Breaking Changes
43+
44+
| Change | Required Action |
45+
|--------|----------------|
46+
| `subjectHash` now required on `DecideRequest` | Compute SHA-256 of canonical subject content and pass as `subjectHash` |
47+
48+
**All callers that construct `DecideRequest` must be updated.** Missing `subjectHash` will raise `ValidationError` at runtime.
49+
50+
---
51+
52+
## Computing `subjectHash`
53+
54+
```python
55+
import hashlib, json
56+
57+
def compute_subject_hash(payload: dict) -> str:
58+
"""JCS canonical hash of the subject payload."""
59+
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
60+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
61+
```
62+
63+
---
64+
65+
## Known Limitations
66+
67+
- `subjectHash` validation is structural only (non-empty string). Hash format (hex, length) is not enforced at SDK level — Keon Runtime validates the binding server-side.
68+
69+
---
70+
71+
## Verify Quickly
72+
73+
```bash
74+
pytest tests/test_keon_sentinels.py -q -m sentinel
75+
```
76+
77+
Expected: all sentinels green including new `subjectHash` tests.
78+
79+
---
80+
81+
## Tags
82+
83+
| Tag | Commit |
84+
|-----|--------|
85+
| `keon-sdk-python-v1.0.0` | `3e20e01` |
86+
| `keon-sdk-python-v1.0.0-rc1` | `3e20e01` |
87+
| `keon-sdk-sentinels-v1.0.0` | sealed prior |

playwright.config.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Playwright configuration — ForgePilot Canonical Test Doctrine v1.0.0
3+
*
4+
* Projects:
5+
* forgepilot-e2e → tests/e2e/forgepilot/ (full user-journey E2E)
6+
* site-smoke → tests/e2e/site/ (keon.systems smoke + link integrity)
7+
* component → tests/component/ (UI unit / truthfulness / obs)
8+
*
9+
* Run targets:
10+
* npx playwright test --project=forgepilot-e2e
11+
* npx playwright test --project=site-smoke
12+
* npx playwright test --project=component
13+
*/
14+
15+
import { defineConfig, devices } from '@playwright/test'
16+
17+
const BASE_URL = process.env['BASE_URL'] ?? 'http://localhost:3000'
18+
const SITE_URL = process.env['SITE_URL'] ?? 'https://keon.systems'
19+
20+
export default defineConfig({
21+
testDir: './tests',
22+
/* Only pick up .spec.ts files — never collide with Python tests/test_*.py */
23+
testMatch: '**/*.spec.ts',
24+
fullyParallel: true,
25+
forbidOnly: !!process.env['CI'],
26+
retries: process.env['CI'] ? 2 : 0,
27+
workers: process.env['CI'] ? 2 : undefined,
28+
timeout: 30_000,
29+
expect: { timeout: 8_000 },
30+
31+
reporter: [
32+
['list'],
33+
['html', { outputFolder: 'playwright-report', open: 'never' }],
34+
['json', { outputFile: 'playwright-report/results.json' }],
35+
],
36+
37+
use: {
38+
baseURL: BASE_URL,
39+
trace: 'on-first-retry',
40+
screenshot: 'only-on-failure',
41+
video: 'retain-on-failure',
42+
},
43+
44+
projects: [
45+
{
46+
name: 'forgepilot-e2e',
47+
testDir: './tests/e2e/forgepilot',
48+
use: {
49+
...devices['Desktop Chrome'],
50+
baseURL: BASE_URL,
51+
},
52+
},
53+
{
54+
name: 'site-smoke',
55+
testDir: './tests/e2e/site',
56+
use: {
57+
...devices['Desktop Chrome'],
58+
baseURL: SITE_URL,
59+
},
60+
},
61+
{
62+
name: 'component',
63+
testDir: './tests/component',
64+
use: {
65+
...devices['Desktop Chrome'],
66+
baseURL: BASE_URL,
67+
},
68+
},
69+
],
70+
71+
/* Start Next.js dev server for local runs against forgepilot-e2e and component */
72+
webServer: {
73+
command: 'npm run dev',
74+
url: BASE_URL,
75+
reuseExistingServer: !process.env['CI'],
76+
timeout: 120_000,
77+
env: {
78+
NODE_ENV: 'test',
79+
},
80+
},
81+
})
82+

run_manifest.schema.json

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://keon.systems/schemas/run_manifest.schema.json",
4+
"title": "RunManifest",
5+
"description": "Canonical evidence record emitted per test run. Schema v1.0.0.",
6+
"type": "object",
7+
"required": [
8+
"schemaVersion",
9+
"runId",
10+
"timestamp",
11+
"env",
12+
"layer",
13+
"suite",
14+
"scenarioId",
15+
"status",
16+
"source",
17+
"evidence"
18+
],
19+
"properties": {
20+
"schemaVersion": {
21+
"type": "string",
22+
"const": "1.0.0",
23+
"description": "Schema version – always 1.0.0 for this revision."
24+
},
25+
"runId": {
26+
"type": "string",
27+
"description": "UUIDv4 uniquely identifying this run attempt.",
28+
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
29+
},
30+
"timestamp": {
31+
"type": "string",
32+
"format": "date-time",
33+
"description": "ISO-8601 UTC timestamp of run start."
34+
},
35+
"env": {
36+
"type": "string",
37+
"enum": ["local", "ci", "staging", "production"],
38+
"description": "Target environment under test."
39+
},
40+
"layer": {
41+
"type": "string",
42+
"description": "Architectural layer under test (e.g. forgepilot.ai, keon.systems)."
43+
},
44+
"suite": {
45+
"type": "string",
46+
"enum": ["smoke", "unit", "contract", "integration", "e2e", "chaos", "perf", "obs"],
47+
"description": "Test suite tier."
48+
},
49+
"scenarioId": {
50+
"type": "string",
51+
"description": "Stable identifier for this test scenario (slug format).",
52+
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$"
53+
},
54+
"status": {
55+
"type": "string",
56+
"enum": ["pass", "fail", "skip", "abort"],
57+
"description": "Terminal status of this run."
58+
},
59+
"durationMs": {
60+
"type": "integer",
61+
"minimum": 0,
62+
"description": "Wall-clock duration of the run in milliseconds."
63+
},
64+
"source": {
65+
"type": "object",
66+
"required": ["file"],
67+
"description": "Origin of the test.",
68+
"properties": {
69+
"file": { "type": "string", "description": "Source file path relative to repo root." },
70+
"line": { "type": "integer", "minimum": 1 },
71+
"commit": { "type": "string", "description": "Git commit SHA (short or full)." },
72+
"branch": { "type": "string" },
73+
"ci": { "type": "string", "description": "CI run URL or job ID if available." }
74+
},
75+
"additionalProperties": false
76+
},
77+
"assertions": {
78+
"type": "array",
79+
"description": "Canonical assertion IDs evaluated during this run.",
80+
"items": {
81+
"type": "object",
82+
"required": ["id", "status"],
83+
"properties": {
84+
"id": {
85+
"type": "string",
86+
"description": "Canonical assertion ID e.g. INV-IDEMPOTENCY.",
87+
"enum": [
88+
"INV-IDEMPOTENCY",
89+
"INV-DETERMINISM",
90+
"INV-ISOLATION",
91+
"INV-TRUTHFULNESS",
92+
"INV-OBSERVABILITY",
93+
"INV-RESILIENCE",
94+
"INV-CONTRACT",
95+
"INV-CORRELATION"
96+
]
97+
},
98+
"status": { "type": "string", "enum": ["pass", "fail", "skip"] },
99+
"message": { "type": "string" }
100+
},
101+
"additionalProperties": false
102+
}
103+
},
104+
"evidence": {
105+
"type": "object",
106+
"required": ["spoolDir"],
107+
"description": "Evidence artifact locations.",
108+
"properties": {
109+
"spoolDir": {
110+
"type": "string",
111+
"description": "Local path to the spool directory containing artifacts."
112+
},
113+
"blobPrefix": {
114+
"type": "string",
115+
"description": "Azure Blob Storage path prefix for this run."
116+
},
117+
"traceUrl": { "type": "string", "format": "uri" },
118+
"screenshotUrls": {
119+
"type": "array",
120+
"items": { "type": "string", "format": "uri" }
121+
}
122+
},
123+
"additionalProperties": false
124+
},
125+
"tags": {
126+
"type": "array",
127+
"items": { "type": "string" },
128+
"description": "Free-form tags for filtering."
129+
},
130+
"meta": {
131+
"type": "object",
132+
"description": "Arbitrary key-value metadata.",
133+
"additionalProperties": { "type": "string" }
134+
}
135+
},
136+
"additionalProperties": false
137+
}
138+
File renamed without changes.
File renamed without changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ class DecideRequest(BaseModel):
5353
action: str = Field(..., min_length=1)
5454
resource_type: str = Field(..., alias="resourceType", min_length=1)
5555
resource_id: str = Field(..., alias="resourceId", min_length=1)
56+
subject_hash: str = Field(
57+
...,
58+
alias="subjectHash",
59+
min_length=1,
60+
description="SHA-256 hex digest of the canonical subject payload (content being governed)",
61+
)
5662
context: Optional[Dict[str, Any]] = None
5763

5864
@field_validator("correlation_id")

0 commit comments

Comments
 (0)