From 9b4b0cbe2de4872e1c290183fecd3c0bb5a64320 Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Thu, 7 May 2026 08:26:48 -0300 Subject: [PATCH 1/2] fix: tolerate install-mutated validate artifacts (#663) --- .aiox-core/install-manifest.yaml | 4 +- ...23.6-validate-mutable-install-artifacts.md | 50 ++++++++++++++ package-lock.json | 4 +- package.json | 2 +- .../src/installer/post-install-validator.js | 27 ++++++++ .../installer/post-install-validator.test.js | 67 +++++++++++++++++++ 6 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md diff --git a/.aiox-core/install-manifest.yaml b/.aiox-core/install-manifest.yaml index 3699fdb0cc..1f6e605432 100644 --- a/.aiox-core/install-manifest.yaml +++ b/.aiox-core/install-manifest.yaml @@ -7,8 +7,8 @@ # - SHA256 hashes for change detection # - File types for categorization # -version: 5.1.3 -generated_at: "2026-05-07T10:57:49.732Z" +version: 5.1.4 +generated_at: "2026-05-07T11:20:24.845Z" generator: scripts/generate-install-manifest.js file_count: 1103 files: diff --git a/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md b/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md new file mode 100644 index 0000000000..bca931703f --- /dev/null +++ b/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md @@ -0,0 +1,50 @@ +# Story 123.6: Validate tolera artefatos mutáveis do install + +## Status + +- [x] Rascunho +- [x] Em implementação +- [x] Concluída + +## Contexto + +O smoke test do pacote publicado `@aiox-squads/core@5.1.3` confirmou que um install limpo sobe o CLI corretamente, mas `aiox validate --json` reporta `corruptedFiles: 2` logo após o próprio `aiox install --force --quiet`. + +Issue GitHub: SynkraAI/aiox-core#663. + +## Objetivo + +Fazer o validador distinguir arquivos de framework imutáveis de artefatos mutáveis que o instalador gera ou mescla durante a instalação, sem reduzir as validações de path, existência e segurança. + +## Acceptance Criteria + +- [x] AC1. `aiox validate` não marca `.aiox-core/core-config.yaml` como corrompido quando o próprio instalador gera ou mescla o arquivo. +- [x] AC2. `aiox validate` não marca `.aiox-core/data/entity-registry.yaml` como corrompido quando o bootstrap da registry popula o arquivo. +- [x] AC3. Arquivos mutáveis continuam validados quanto a existência, contenção de path, symlink e tipo regular. +- [x] AC4. Hash/tamanho continuam rígidos para arquivos não mutáveis do framework. +- [x] AC5. Smoke test publicado ou local equivalente termina com `corruptedFiles: 0`. + +## Tasks + +- [x] Adicionar classificação explícita de artefatos mutáveis no `PostInstallValidator`. +- [x] Ignorar comparação rígida de hash/tamanho apenas para os paths mutáveis conhecidos. +- [x] Cobrir regressão com teste unitário de install manifest pós-mutação. +- [x] Rodar gates locais focados e completos antes de abrir PR. + +## Execution + +- `npm test -- tests/installer/post-install-validator.test.js --runInBand` ✓ +- `node -c packages/installer/src/installer/post-install-validator.js` ✓ +- `npm run generate:manifest && npm run validate:manifest` ✓ +- Smoke de pacote local `@aiox-squads/core@5.1.4`: + - `npx --yes aiox --version` → `5.1.4` + - `npx --yes aiox validate --json` → `status: success`, `validFiles: 1103`, `corruptedFiles: 0`, `issueCount: 0` + +## File List + +- [docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md](./STORY-123.6-validate-mutable-install-artifacts.md) +- [.aiox-core/install-manifest.yaml](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/.aiox-core/install-manifest.yaml) +- [package.json](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/package.json) +- [package-lock.json](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/package-lock.json) +- [packages/installer/src/installer/post-install-validator.js](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/packages/installer/src/installer/post-install-validator.js) +- [tests/installer/post-install-validator.test.js](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/tests/installer/post-install-validator.test.js) diff --git a/package-lock.json b/package-lock.json index a400a9e03c..3be3b16448 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aiox-squads/core", - "version": "5.1.3", + "version": "5.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aiox-squads/core", - "version": "5.1.3", + "version": "5.1.4", "license": "MIT", "workspaces": [ "packages/*" diff --git a/package.json b/package.json index 84655b9777..14e186a8e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aiox-squads/core", - "version": "5.1.3", + "version": "5.1.4", "description": "Synkra AIOX: AI-Orchestrated System for Full Stack Development - Core Framework", "bin": { "aiox": "bin/aiox.js", diff --git a/packages/installer/src/installer/post-install-validator.js b/packages/installer/src/installer/post-install-validator.js index a2f337d5ad..e798e952a3 100644 --- a/packages/installer/src/installer/post-install-validator.js +++ b/packages/installer/src/installer/post-install-validator.js @@ -128,6 +128,11 @@ const ALLOWED_TYPE_VALUES = [ 'manifest', ]; +const PROJECT_MUTABLE_MANIFEST_PATHS = new Set([ + 'core-config.yaml', + 'data/entity-registry.yaml', +]); + /** * Categorize a file path into its functional category * @param {string} filePath - Relative file path @@ -169,6 +174,19 @@ function getSeverityForCategory(category) { return severityMap[category] || Severity.LOW; } +/** + * Determine whether a manifest path is expected to be mutated by install/bootstrap. + * These files still go through path/existence/symlink validation, but their content + * is project-local after install and must not be hash-compared to the package copy. + * + * @param {string} filePath - Relative file path from .aiox-core + * @returns {boolean} True when the file is project mutable + */ +function isProjectMutableManifestPath(filePath) { + const normalized = String(filePath || '').replace(/\\/g, '/'); + return PROJECT_MUTABLE_MANIFEST_PATHS.has(normalized); +} + /** * Validate that a resolved path is contained within the root directory * Prevents path traversal attacks via malicious manifest entries @@ -787,6 +805,12 @@ class PostInstallValidator { result.exists = true; + if (isProjectMutableManifestPath(relativePath)) { + result.projectMutable = true; + this.stats.validFiles++; + return result; + } + // SECURITY [H2]: In quick mode (no hash), size MUST be present if (!this.options.verifyHashes) { if (entry.size === null || entry.size === undefined) { @@ -1077,6 +1101,8 @@ class PostInstallValidator { if (issuesBySeverity[Severity.CRITICAL].length > 0) { status = 'failed'; } else if ( + this.stats.missingFiles > 0 || + this.stats.corruptedFiles > 0 || issuesBySeverity[Severity.HIGH].length > 0 || issuesBySeverity[Severity.MEDIUM].length > 0 ) { @@ -1521,5 +1547,6 @@ module.exports = { FileCategory, SecurityLimits, isPathContained, + isProjectMutableManifestPath, validateManifestEntry, }; diff --git a/tests/installer/post-install-validator.test.js b/tests/installer/post-install-validator.test.js index 2f6d41f839..3ae12cbb7b 100644 --- a/tests/installer/post-install-validator.test.js +++ b/tests/installer/post-install-validator.test.js @@ -24,6 +24,7 @@ const { IssueType, Severity, SecurityLimits, + isProjectMutableManifestPath, } = require('../../packages/installer/src/installer/post-install-validator'); describe('PostInstallValidator Security Tests', () => { @@ -360,6 +361,72 @@ describe('PostInstallValidator Security Tests', () => { }); }); + describe('Project Mutable Install Artifacts', () => { + test('should classify only install-mutated manifest paths as project mutable', () => { + expect(isProjectMutableManifestPath('core-config.yaml')).toBe(true); + expect(isProjectMutableManifestPath('data/entity-registry.yaml')).toBe(true); + expect(isProjectMutableManifestPath('data/capability-detection.js')).toBe(false); + expect(isProjectMutableManifestPath('core/config.js')).toBe(false); + }); + + test('should not mark installer-mutated files as corrupted after content changes', async () => { + const manifest = `version: "1.0.0" +files: + - path: core-config.yaml + hash: "sha256:${'a'.repeat(64)}" + size: 10 + - path: data/entity-registry.yaml + hash: "sha256:${'b'.repeat(64)}" + size: 10`; + + await fs.ensureDir(path.join(targetDir, '.aiox-core', 'data')); + await fs.writeFile(path.join(targetDir, '.aiox-core', 'install-manifest.yaml'), manifest); + await fs.writeFile(path.join(targetDir, '.aiox-core', 'core-config.yaml'), 'project:\n name: generated\n'); + await fs.writeFile( + path.join(targetDir, '.aiox-core', 'data', 'entity-registry.yaml'), + 'metadata:\n entityCount: 757\n updatedAt: now\n', + ); + + const validator = new PostInstallValidator(targetDir, null, { + requireSignature: false, + verifyHashes: true, + }); + + const report = await validator.validate(); + + expect(report.status).toBe('success'); + expect(report.stats.validFiles).toBe(2); + expect(report.stats.corruptedFiles).toBe(0); + expect(report.issues).toHaveLength(0); + }); + + test('should still report corruption for non-mutable manifest paths', async () => { + const manifest = `version: "1.0.0" +files: + - path: data/capability-detection.js + hash: "sha256:${'c'.repeat(64)}" + size: 10`; + + await fs.ensureDir(path.join(targetDir, '.aiox-core', 'data')); + await fs.writeFile(path.join(targetDir, '.aiox-core', 'install-manifest.yaml'), manifest); + await fs.writeFile( + path.join(targetDir, '.aiox-core', 'data', 'capability-detection.js'), + 'module.exports = {};\n', + ); + + const validator = new PostInstallValidator(targetDir, null, { + requireSignature: false, + verifyHashes: true, + }); + + const report = await validator.validate(); + + expect(report.status).toBe('warning'); + expect(report.stats.corruptedFiles).toBe(1); + expect(report.issues[0].type).toBe(IssueType.CORRUPTED_FILE); + }); + }); + describe('Secure Repair (C4)', () => { test('should refuse repair without hash verification', async () => { const validator = new PostInstallValidator(targetDir, sourceDir, { From a1efc25f2519485bb23c643650d5e06155f8a129 Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Thu, 7 May 2026 08:35:12 -0300 Subject: [PATCH 2/2] docs: use repo-relative story file links (#663) --- .../STORY-123.6-validate-mutable-install-artifacts.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md b/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md index bca931703f..5414d3fc8c 100644 --- a/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md +++ b/docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md @@ -43,8 +43,8 @@ Fazer o validador distinguir arquivos de framework imutáveis de artefatos mutá ## File List - [docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md](./STORY-123.6-validate-mutable-install-artifacts.md) -- [.aiox-core/install-manifest.yaml](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/.aiox-core/install-manifest.yaml) -- [package.json](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/package.json) -- [package-lock.json](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/package-lock.json) -- [packages/installer/src/installer/post-install-validator.js](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/packages/installer/src/installer/post-install-validator.js) -- [tests/installer/post-install-validator.test.js](/Users/rafaelcosta/Projects/AIOX/aiox-core-pro-13-2/tests/installer/post-install-validator.test.js) +- [.aiox-core/install-manifest.yaml](../../../.aiox-core/install-manifest.yaml) +- [package.json](../../../package.json) +- [package-lock.json](../../../package-lock.json) +- [packages/installer/src/installer/post-install-validator.js](../../../packages/installer/src/installer/post-install-validator.js) +- [tests/installer/post-install-validator.test.js](../../../tests/installer/post-install-validator.test.js)