-
-
Notifications
You must be signed in to change notification settings - Fork 38.4k
feat: add read-only Lean/Full context profile contracts #3037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 10 commits
cd3950d
4c1b938
5fcd9e0
f01d336
254a6cc
0dbe3a9
88f5a99
78cbd01
7787297
c1da574
c373b7f
ea00894
9b5e393
5f9503e
e54fd44
be393cb
563e70a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # ECC-029 read-only context profile evidence | ||
|
|
||
| Date: September 8, 2026. Scope: the first P0/P1 implementation slice for M1, canonical context profiles. Baseline: main `5064474d4d762dc9640234a41617cccb79185cec`, ECC 2.2.1. Environment: macOS 26.6.2, Apple M4 Pro, Node 24.9.0. This is local development evidence, not a release or native-host certification. | ||
|
|
||
| Source intent: the accepted ECC-029 production and economics planning canvases in the maintainer workspace. Their approved first-slice journeys and boundaries are carried into the portable [implementation contract](context-profiles.md). Planning text was treated as design input; validation used reviewed local test, lint, package, and inspection commands. No activation, remote installer, publication, or credential-handling instruction was adopted. The project detector selected unavailable Bun; the actual test scripts run standalone Node, so Node and npm ran them without changing package-manager preferences. | ||
|
|
||
| ## Journeys and test specification | ||
|
|
||
| | Approved journey and guarantee | Test target | Type | RED evidence | GREEN evidence | | ||
| | --- | --- | --- | --- | --- | | ||
| | Inspect versioned profiles and exact skill IDs without invoking skills or changing caller state | [CLI tests](../../tests/scripts/profile.test.js) | CLI journey/integration | `cd3950d3`: 24 failures for the missing command, entrypoint, and package inclusion | 25 passed, including later terminal-control regression; temporary home and workspace snapshots remain unchanged | | ||
| | Build one portable canonical skill inventory with validated ownership, explicit declarations, and resource digests | [Registry tests](../../tests/lib/context-pack-registry.test.js) | Unit/integration | `4c1b938b`: intended registry module absent | 15 passed, including source safety and repository inventory | | ||
| | Compile deterministic Lean/Full proposals with exact selectors, declared dependency closure, and honest metadata estimates | [Profile tests](../../tests/lib/context-profiles.test.js) | Unit/integration | `4c1b938b`: intended compiler module absent | 12 passed; 8,000 passes and 8,001 blocks the Lean metadata estimator, while native totals remain unknown | | ||
| | Gate every recognized target and register validation in the normal test workflow | [CI tests](../../tests/ci/context-profiles.test.js) | Integration | `5fcd9e08`: 3 failures for missing validation and registration | 3 passed; 2 profiles across 16 target IDs | | ||
| | Reject redirected source reads, unsafe metadata controls, and unstable cache-derived provenance | Registry and profile tests above | Security/regression | `f01d3366`: 23 passed and 3 expected failures during review | Same regressions pass; redirected descriptor receives zero byte reads in the substitution fixture | | ||
| | Keep user-supplied terminal controls inert in CLI error output | CLI tests above | Security/CLI | `254a6cc1`: 24 passed, 1 failed for raw OSC output | 25 passed | | ||
| | Ship the entrypoint, libraries, schemas, manifests, and contract together | [Publish-surface tests](../../tests/scripts/npm-publish-surface.test.js) | Packaging/integration | Existing explicit publish allowlist initially reported 1 pass and 1 failure | Updated expected public surface passes, plus real offline package smoke below | | ||
|
|
||
| The module-absence RED runs exercised the intended new public entry points; they were not failures of an unrelated dependency installation. The initial library checkpoint contained 20 cases; boundary and security review grew the focused library suite to 27. All listed checkpoints are local commits on `plan/ecc-029-harness-scoping`, reachable from the GREEN implementation commit. Preserve this record if later integration squashes those checkpoints. No separate refactor stage was performed after final GREEN validation. | ||
|
|
||
| ## Executed checks | ||
|
|
||
| ```sh | ||
| node --test tests/lib/context-pack-registry.test.js tests/lib/context-profiles.test.js | ||
| node tests/scripts/profile.test.js | ||
| node tests/ci/context-profiles.test.js | ||
| node tests/scripts/npm-publish-surface.test.js | ||
| npm run context-profiles:check | ||
| npm test | ||
| npm run lint | ||
| git diff --check | ||
| ``` | ||
|
|
||
| Final focused coverage execution also runs the first four feature test targets together: | ||
|
|
||
| ```sh | ||
| ./node_modules/.bin/c8 --all \ | ||
| --include='scripts/lib/context*.js' \ | ||
| --include='scripts/profile.js' \ | ||
| --include='scripts/ci/validate-context-profiles.js' \ | ||
| --reporter=text --reporter=json-summary \ | ||
| --reports-dir=/tmp/ecc-029-context-coverage \ | ||
| --check-coverage --lines=80 --functions=80 --branches=80 --statements=80 \ | ||
| node --test tests/lib/context-pack-registry.test.js \ | ||
| tests/lib/context-profiles.test.js tests/scripts/profile.test.js \ | ||
| tests/ci/context-profiles.test.js | ||
| ``` | ||
|
|
||
| Results: 27 library cases, 25 CLI cases, and 3 CI cases passed. Node's outer TAP summary reports 29 because the CLI and CI files each wrap their own cases. New-code coverage is 98.43% statements and lines, 90% branches, and 100% functions. Coverage thresholds all pass; no focused cases were skipped. Uncovered lines include a defensive source-error path and the single-profile text rendering branch. | ||
|
|
||
| The complete `npm test` command exited 0 and its legacy aggregate reported `Total Tests: 4423`, `Passed: 4423`, `Failed: 0`. Its aggregate does not separately count the new node:test library cases, which have their explicit result above. Existing platform-dependent tests can skip on macOS; this run supplies no Windows or Linux execution evidence. Full ESLint/Markdown lint, catalog/command validators, and whitespace checks passed. | ||
|
|
||
| ## Packed offline user journey | ||
|
|
||
| Ran `npm pack` with the real prepack build into a disposable directory, followed by `npm install --offline --ignore-scripts --omit=dev --no-audit --no-fund --userconfig=/dev/null` into a disposable consumer. The install succeeded using cached dependencies. No package was published or globally installed. | ||
|
|
||
| The packaged dispatcher produced Lean and Full Codex previews, and the packaged direct entrypoint explained an exact skill ID. Both full proposed-plan objects were deeply equal to their checkout counterparts, including registry, profile, compiler, and plan digests. The subprocess environment used an explicit allowlist and a disposable user-home path, which remained absent after all three calls. This checks the real archive and runtime dependencies independently of the checkout's module resolution. | ||
|
|
||
| At this baseline, Codex Lean selects 3 entries and leaves 283 routed; Full selects all 286. The descriptor estimator reports 221 tokens from 879 bytes for Lean and 26,145 tokens from 104,168 bytes for Full. These are reproducible fixture estimates, not observed native startup tokens or demonstrated task savings. | ||
|
|
||
| ## Review findings and remaining gates | ||
|
|
||
| Independent review reproduced ancestor substitution and terminal-control issues before fixes, then rechecked the fixes and approved the read-only boundary. Source identity checks do not create an atomic filesystem snapshot, and directory listing size is not independently bounded. Dependency coverage remains explicit-declarations-only and unreviewed. Required-resource annotations need a distinct output contract before selective P2 carriers can safely omit resources. | ||
|
|
||
| The existing js-yaml security update in contributor [PR #3032](https://github.com/affaan-m/ECC/pull/3032) must be verified and integrated before release. The new JSON-schema parsing path excludes the advisory's merge feature, but that does not clear existing default-schema parsers. See the [contract's dependency gate](context-profiles.md#contributor-integration-lanes). | ||
|
|
||
| Native carriers, active discovery, actual skill invocation, transactional activation, hook consent, automatic task routing, recovery, real-host token counters, broader context surfaces, cross-platform conformance, and default migration remain follow-on work. No provider calls, container or VM launches, or runtime profile changes were used to establish these results. | ||
|
|
||
| ## PR-readiness follow-up | ||
|
|
||
| Independent exact-head review approved the read-only implementation and identified privilege-sensitive symlink fixtures. Review's original permission-denial injection produced 12 passes and 3 failures. Checkpoint `88f5a996` added a failing portable directory-link contract: 15 passes and 1 expected failure. The fix uses Windows junctions for directory cases, separates unconditional ownership and mocked leaf-link rejection from the real file-link integration case, and explicitly skips only that extra file-link case on Windows EPERM/EACCES. No runtime code changed. | ||
|
|
||
| Final local focused checks now pass 30 library, 25 CLI, and 3 CI cases. A bounded simulation of Windows file-link denial, keeping the local temporary directory fixed and emulating directory junctions, passes 17 registry cases and explicitly skips 1 real file-link case. It is a test-policy simulation, not native Windows evidence. The source-read substitution and zero-byte-read assertions remain mandatory. | ||
|
|
||
| An isolated Git archive passed `YARN_ENABLE_HARDENED_MODE=1 YARN_ENABLE_SCRIPTS=false yarn install --immutable --mode=skip-build`; both package manifest and Yarn lockfile remained byte-identical. The initially attempted immutable/update-lockfile combination was rejected by Yarn as incompatible before installation; the immutable skip-build run is the applicable successful CI check. Dependency declarations remain unchanged. Source-only evidence/test links in the shipped contract are now labeled explicitly. | ||
|
|
||
| ### Contributor security prerequisite | ||
|
|
||
| Hosted CI for PR #3037 at `78cbd01c` reproduced the existing js-yaml high-severity advisory in its runtime audit. The branch incorporated contributor Myles Agnew's exact commit `5674661fc30ab1d3f3fcae22d72bfb4ab3059822` from #3032 using an attributed cherry-pick (`77872972`). No contributor PR was merged or closed. A fresh dependency install resolved js-yaml 4.3.2, and `npm audit --omit=dev --audit-level=high` reports zero vulnerabilities. | ||
|
|
||
| The local npm 11 install unexpectedly rewrote the Yarn lock into its legacy format. Only that task-induced rewrite was restored to the committed contributor bytes before subsequent validation. This is installation-tool behavior, not an intended lockfile change. The full test run started on the preceding revision overlapped the dependency update and is excluded from exact-final-head evidence; final PR checks must bind to the updated head. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "schemaVersion": 1, | ||
| "id": "skill-registry@1", | ||
| "inventory": { | ||
| "source": "manifests/install-modules.json", | ||
| "skillsRoot": "skills" | ||
| }, | ||
| "overrides": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "schemaVersion": 1, | ||
| "id": "full@1", | ||
| "description": "Proposed complete canonical skill discovery projection. Agents, commands, rules, hooks and tool schemas remain outside this projection; native activation is unobserved.", | ||
| "registryId": "skill-registry@1", | ||
| "selection": { | ||
| "eager": "all", | ||
| "required": ["skill:configure-ecc", "skill:context-budget", "skill:ecc-guide"], | ||
| "remainder": "routed" | ||
| }, | ||
| "budget": { "tokens": 8000, "mode": "report-only" } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "schemaVersion": 1, | ||
| "id": "lean@1", | ||
| "description": "Proposed three-skill ECC discovery kernel. Remaining skills are routed; this profile does not activate or modify a harness.", | ||
| "registryId": "skill-registry@1", | ||
| "selection": { | ||
| "eager": ["skill:configure-ecc", "skill:context-budget", "skill:ecc-guide"], | ||
| "required": ["skill:configure-ecc", "skill:context-budget", "skill:ecc-guide"], | ||
| "remainder": "routed" | ||
| }, | ||
| "budget": { "tokens": 8000, "mode": "blocking" } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/draft-07/schema#", | ||
| "title": "ECC context registry declaration", | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["schemaVersion", "id", "inventory", "overrides"], | ||
| "properties": { | ||
| "schemaVersion": { "const": 1 }, | ||
| "id": { "const": "skill-registry@1" }, | ||
| "inventory": { | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["source", "skillsRoot"], | ||
| "properties": { | ||
| "source": { "const": "manifests/install-modules.json" }, | ||
| "skillsRoot": { "const": "skills" } | ||
| } | ||
| }, | ||
| "overrides": { | ||
| "type": "array", | ||
| "items": { | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["id"], | ||
| "properties": { | ||
| "id": { "$ref": "#/definitions/skillId" }, | ||
| "dependencies": { | ||
| "type": "array", "uniqueItems": true, | ||
| "items": { "$ref": "#/definitions/skillId" } | ||
| }, | ||
| "requiredResources": { | ||
| "type": "array", "uniqueItems": true, | ||
| "items": { "type": "string", "minLength": 1, "maxLength": 4096 } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "definitions": { | ||
| "skillId": { "type": "string", "pattern": "^skill:[a-z0-9]+(?:-[a-z0-9]+)*$" } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/draft-07/schema#", | ||
| "title": "ECC read-only context profile", | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["schemaVersion", "id", "description", "registryId", "selection", "budget"], | ||
| "properties": { | ||
| "schemaVersion": { "const": 1 }, | ||
| "id": { "enum": ["lean@1", "full@1"] }, | ||
| "description": { "type": "string", "minLength": 1, "maxLength": 2000 }, | ||
| "registryId": { "const": "skill-registry@1" }, | ||
| "selection": { | ||
| "type": "object", "additionalProperties": false, | ||
| "required": ["eager", "required", "remainder"], | ||
| "properties": { | ||
| "eager": { "oneOf": [{ "const": "all" }, { "$ref": "#/definitions/skillIds" }] }, | ||
| "required": { "$ref": "#/definitions/skillIds" }, | ||
| "remainder": { "const": "routed" } | ||
| } | ||
| }, | ||
| "budget": { | ||
| "type": "object", "additionalProperties": false, | ||
| "required": ["tokens", "mode"], | ||
| "properties": { | ||
| "tokens": { "const": 8000 }, | ||
| "mode": { "enum": ["blocking", "report-only"] } | ||
| } | ||
| } | ||
| }, | ||
| "definitions": { | ||
| "skillIds": { | ||
| "type": "array", "uniqueItems": true, | ||
| "items": { "type": "string", "pattern": "^skill:[a-z0-9]+(?:-[a-z0-9]+)*$" } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||||||||
| #!/usr/bin/env node | ||||||||||||||||||||||||||||||
| 'use strict'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const { loadContextRegistry } = require('../lib/context-pack-registry'); | ||||||||||||||||||||||||||||||
| const { compileContextProfile } = require('../lib/context-profiles'); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function validate(repoRoot) { | ||||||||||||||||||||||||||||||
| const registry = loadContextRegistry({ repoRoot }); | ||||||||||||||||||||||||||||||
| const profiles = ['lean@1', 'full@1']; | ||||||||||||||||||||||||||||||
| for (const profileId of profiles) { | ||||||||||||||||||||||||||||||
| for (const target of registry.targets) { | ||||||||||||||||||||||||||||||
| compileContextProfile({ repoRoot, profileId, target }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Name the failing profile and target in the validation error. The loop compiles 32 projections. If one projection throws, the catch at line 33 prints only ♻️ Proposed fix to add failure context for (const profileId of profiles) {
for (const target of registry.targets) {
- compileContextProfile({ repoRoot, profileId, target });
+ try {
+ compileContextProfile({ repoRoot, profileId, target });
+ } catch (error) {
+ throw new Error(`Profile ${profileId} failed for target ${target}: ${error.message}`);
+ }
}
}Note also that lines 29-30 and 33 use As per coding guidelines, "Always handle errors explicitly at every level and never silently swallow errors" and "No console.log statements in production code; use proper logging libraries instead". 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| status: 'success', skillCount: registry.entries.length, | ||||||||||||||||||||||||||||||
| profileCount: profiles.length, targetCount: registry.targets.length, | ||||||||||||||||||||||||||||||
| projectionCount: profiles.length * registry.targets.length, | ||||||||||||||||||||||||||||||
| registryDigest: registry.registryDigest, nativeCertification: 'unobserved', | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function main(args = process.argv.slice(2)) { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| for (const arg of args) { | ||||||||||||||||||||||||||||||
| if (arg !== '--json') throw new Error(`Unknown argument: ${arg}`); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| const result = validate(); | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Pass an explicit repository root into
♻️ Proposed fix+const path = require('path');
+
+const DEFAULT_ROOT = path.resolve(__dirname, '../..');
+
-function validate(repoRoot) {
+function validate(repoRoot = DEFAULT_ROOT) {- const result = validate();
+ const result = validate(DEFAULT_ROOT);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| console.log(args.includes('--json') ? JSON.stringify(result, null, 2) | ||||||||||||||||||||||||||||||
| : `Context profiles valid: ${result.skillCount} skills, ${result.projectionCount} profile/target projections. Native certification: unobserved.`); | ||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||
| console.error(`Context profile validation failed: ${error.message}`); | ||||||||||||||||||||||||||||||
| return 1; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (require.main === module) process.exitCode = main(); | ||||||||||||||||||||||||||||||
| module.exports = { main, validate }; | ||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.