Skip to content

Commit 2dac962

Browse files
authored
fix: tolerate install-mutated validate artifacts (#664)
* fix: tolerate install-mutated validate artifacts (#663) * docs: use repo-relative story file links (#663)
1 parent 80485c6 commit 2dac962

6 files changed

Lines changed: 149 additions & 5 deletions

File tree

.aiox-core/install-manifest.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
# - SHA256 hashes for change detection
88
# - File types for categorization
99
#
10-
version: 5.1.3
11-
generated_at: "2026-05-07T10:57:49.732Z"
10+
version: 5.1.4
11+
generated_at: "2026-05-07T11:20:24.845Z"
1212
generator: scripts/generate-install-manifest.js
1313
file_count: 1103
1414
files:
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Story 123.6: Validate tolera artefatos mutáveis do install
2+
3+
## Status
4+
5+
- [x] Rascunho
6+
- [x] Em implementação
7+
- [x] Concluída
8+
9+
## Contexto
10+
11+
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`.
12+
13+
Issue GitHub: SynkraAI/aiox-core#663.
14+
15+
## Objetivo
16+
17+
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.
18+
19+
## Acceptance Criteria
20+
21+
- [x] AC1. `aiox validate` não marca `.aiox-core/core-config.yaml` como corrompido quando o próprio instalador gera ou mescla o arquivo.
22+
- [x] AC2. `aiox validate` não marca `.aiox-core/data/entity-registry.yaml` como corrompido quando o bootstrap da registry popula o arquivo.
23+
- [x] AC3. Arquivos mutáveis continuam validados quanto a existência, contenção de path, symlink e tipo regular.
24+
- [x] AC4. Hash/tamanho continuam rígidos para arquivos não mutáveis do framework.
25+
- [x] AC5. Smoke test publicado ou local equivalente termina com `corruptedFiles: 0`.
26+
27+
## Tasks
28+
29+
- [x] Adicionar classificação explícita de artefatos mutáveis no `PostInstallValidator`.
30+
- [x] Ignorar comparação rígida de hash/tamanho apenas para os paths mutáveis conhecidos.
31+
- [x] Cobrir regressão com teste unitário de install manifest pós-mutação.
32+
- [x] Rodar gates locais focados e completos antes de abrir PR.
33+
34+
## Execution
35+
36+
- `npm test -- tests/installer/post-install-validator.test.js --runInBand`
37+
- `node -c packages/installer/src/installer/post-install-validator.js`
38+
- `npm run generate:manifest && npm run validate:manifest`
39+
- Smoke de pacote local `@aiox-squads/core@5.1.4`:
40+
- `npx --yes aiox --version``5.1.4`
41+
- `npx --yes aiox validate --json``status: success`, `validFiles: 1103`, `corruptedFiles: 0`, `issueCount: 0`
42+
43+
## File List
44+
45+
- [docs/stories/epic-123/STORY-123.6-validate-mutable-install-artifacts.md](./STORY-123.6-validate-mutable-install-artifacts.md)
46+
- [.aiox-core/install-manifest.yaml](../../../.aiox-core/install-manifest.yaml)
47+
- [package.json](../../../package.json)
48+
- [package-lock.json](../../../package-lock.json)
49+
- [packages/installer/src/installer/post-install-validator.js](../../../packages/installer/src/installer/post-install-validator.js)
50+
- [tests/installer/post-install-validator.test.js](../../../tests/installer/post-install-validator.test.js)

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aiox-squads/core",
3-
"version": "5.1.3",
3+
"version": "5.1.4",
44
"description": "Synkra AIOX: AI-Orchestrated System for Full Stack Development - Core Framework",
55
"bin": {
66
"aiox": "bin/aiox.js",

packages/installer/src/installer/post-install-validator.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ const ALLOWED_TYPE_VALUES = [
128128
'manifest',
129129
];
130130

131+
const PROJECT_MUTABLE_MANIFEST_PATHS = new Set([
132+
'core-config.yaml',
133+
'data/entity-registry.yaml',
134+
]);
135+
131136
/**
132137
* Categorize a file path into its functional category
133138
* @param {string} filePath - Relative file path
@@ -169,6 +174,19 @@ function getSeverityForCategory(category) {
169174
return severityMap[category] || Severity.LOW;
170175
}
171176

177+
/**
178+
* Determine whether a manifest path is expected to be mutated by install/bootstrap.
179+
* These files still go through path/existence/symlink validation, but their content
180+
* is project-local after install and must not be hash-compared to the package copy.
181+
*
182+
* @param {string} filePath - Relative file path from .aiox-core
183+
* @returns {boolean} True when the file is project mutable
184+
*/
185+
function isProjectMutableManifestPath(filePath) {
186+
const normalized = String(filePath || '').replace(/\\/g, '/');
187+
return PROJECT_MUTABLE_MANIFEST_PATHS.has(normalized);
188+
}
189+
172190
/**
173191
* Validate that a resolved path is contained within the root directory
174192
* Prevents path traversal attacks via malicious manifest entries
@@ -787,6 +805,12 @@ class PostInstallValidator {
787805

788806
result.exists = true;
789807

808+
if (isProjectMutableManifestPath(relativePath)) {
809+
result.projectMutable = true;
810+
this.stats.validFiles++;
811+
return result;
812+
}
813+
790814
// SECURITY [H2]: In quick mode (no hash), size MUST be present
791815
if (!this.options.verifyHashes) {
792816
if (entry.size === null || entry.size === undefined) {
@@ -1077,6 +1101,8 @@ class PostInstallValidator {
10771101
if (issuesBySeverity[Severity.CRITICAL].length > 0) {
10781102
status = 'failed';
10791103
} else if (
1104+
this.stats.missingFiles > 0 ||
1105+
this.stats.corruptedFiles > 0 ||
10801106
issuesBySeverity[Severity.HIGH].length > 0 ||
10811107
issuesBySeverity[Severity.MEDIUM].length > 0
10821108
) {
@@ -1521,5 +1547,6 @@ module.exports = {
15211547
FileCategory,
15221548
SecurityLimits,
15231549
isPathContained,
1550+
isProjectMutableManifestPath,
15241551
validateManifestEntry,
15251552
};

tests/installer/post-install-validator.test.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const {
2424
IssueType,
2525
Severity,
2626
SecurityLimits,
27+
isProjectMutableManifestPath,
2728
} = require('../../packages/installer/src/installer/post-install-validator');
2829

2930
describe('PostInstallValidator Security Tests', () => {
@@ -360,6 +361,72 @@ describe('PostInstallValidator Security Tests', () => {
360361
});
361362
});
362363

364+
describe('Project Mutable Install Artifacts', () => {
365+
test('should classify only install-mutated manifest paths as project mutable', () => {
366+
expect(isProjectMutableManifestPath('core-config.yaml')).toBe(true);
367+
expect(isProjectMutableManifestPath('data/entity-registry.yaml')).toBe(true);
368+
expect(isProjectMutableManifestPath('data/capability-detection.js')).toBe(false);
369+
expect(isProjectMutableManifestPath('core/config.js')).toBe(false);
370+
});
371+
372+
test('should not mark installer-mutated files as corrupted after content changes', async () => {
373+
const manifest = `version: "1.0.0"
374+
files:
375+
- path: core-config.yaml
376+
hash: "sha256:${'a'.repeat(64)}"
377+
size: 10
378+
- path: data/entity-registry.yaml
379+
hash: "sha256:${'b'.repeat(64)}"
380+
size: 10`;
381+
382+
await fs.ensureDir(path.join(targetDir, '.aiox-core', 'data'));
383+
await fs.writeFile(path.join(targetDir, '.aiox-core', 'install-manifest.yaml'), manifest);
384+
await fs.writeFile(path.join(targetDir, '.aiox-core', 'core-config.yaml'), 'project:\n name: generated\n');
385+
await fs.writeFile(
386+
path.join(targetDir, '.aiox-core', 'data', 'entity-registry.yaml'),
387+
'metadata:\n entityCount: 757\n updatedAt: now\n',
388+
);
389+
390+
const validator = new PostInstallValidator(targetDir, null, {
391+
requireSignature: false,
392+
verifyHashes: true,
393+
});
394+
395+
const report = await validator.validate();
396+
397+
expect(report.status).toBe('success');
398+
expect(report.stats.validFiles).toBe(2);
399+
expect(report.stats.corruptedFiles).toBe(0);
400+
expect(report.issues).toHaveLength(0);
401+
});
402+
403+
test('should still report corruption for non-mutable manifest paths', async () => {
404+
const manifest = `version: "1.0.0"
405+
files:
406+
- path: data/capability-detection.js
407+
hash: "sha256:${'c'.repeat(64)}"
408+
size: 10`;
409+
410+
await fs.ensureDir(path.join(targetDir, '.aiox-core', 'data'));
411+
await fs.writeFile(path.join(targetDir, '.aiox-core', 'install-manifest.yaml'), manifest);
412+
await fs.writeFile(
413+
path.join(targetDir, '.aiox-core', 'data', 'capability-detection.js'),
414+
'module.exports = {};\n',
415+
);
416+
417+
const validator = new PostInstallValidator(targetDir, null, {
418+
requireSignature: false,
419+
verifyHashes: true,
420+
});
421+
422+
const report = await validator.validate();
423+
424+
expect(report.status).toBe('warning');
425+
expect(report.stats.corruptedFiles).toBe(1);
426+
expect(report.issues[0].type).toBe(IssueType.CORRUPTED_FILE);
427+
});
428+
});
429+
363430
describe('Secure Repair (C4)', () => {
364431
test('should refuse repair without hash verification', async () => {
365432
const validator = new PostInstallValidator(targetDir, sourceDir, {

0 commit comments

Comments
 (0)