Repository navigation
feat(six-encounters): Wave 5 end-to-end Six Encounters publication package assembly #68
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -305,16 +305,246 @@ | |
| }; | ||
| } | ||
|
|
||
| function freeze(o) { | ||
| if (o && typeof o === 'object') { | ||
| Object.values(o).forEach(v => { | ||
| if (v && typeof v === 'object' && !Object.isFrozen(v)) freeze(v); | ||
| }); | ||
| return Object.freeze(o); | ||
| } | ||
| return o; | ||
| } | ||
|
|
||
| function getGeneration() { | ||
| if (root.HLGeneration) return root.HLGeneration; | ||
| if (typeof module !== 'undefined' && typeof require !== 'undefined') { | ||
| try { return require('./generation.js'); } catch (_) {} | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| function getSensory() { | ||
| if (root.HLSensoryAccessibility) return root.HLSensoryAccessibility; | ||
| if (typeof module !== 'undefined' && typeof require !== 'undefined') { | ||
| try { return require('./sensory-accessibility.js'); } catch (_) {} | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| // ------------------------------------------------------------- | ||
| // End-to-End Publication Package Assembly | ||
| // ------------------------------------------------------------- | ||
| function assembleSixEncountersPackage(sessionInput = {}) { | ||
| const input = (sessionInput && typeof sessionInput === 'object') ? sessionInput : {}; | ||
| const version = String(input.version || input.edition || '1.2.0'); | ||
| const commit = String(input.commit || '7f4e5f9610701cd1cb7e398f0b66a6e26c8d35e0'); | ||
| const seed = typeof input.seed === 'number' ? input.seed : 42; | ||
| const chamber = String(input.chamber || '040915'); | ||
|
|
||
| const G = getGeneration(); | ||
| const SA = getSensory(); | ||
|
|
||
| // 1. C2: Patch validation & compilation | ||
| const rawPatch = input.patch || { | ||
| source: 'six-encounters-canonical', | ||
| topology: { chamber, defaultChamber: '041015' }, | ||
| geometry: { scale: 1.0, width: 1440, height: 1000 }, | ||
| typography: { family: 'monospace', size: 18, weight: 400 }, | ||
| palette: { background: '#000000', foreground: '#00ffff' }, | ||
| temporalScore: { exposure: 5000, entry: 450, exit: 350 }, | ||
| interactionRules: { | ||
| executableActions: [ | ||
| { type: 'set_property', key: 'chamber', value: chamber } | ||
| ], | ||
| poeticInstructions: [ | ||
| 'where i end and u begin', | ||
| 'through the six encounters of the loop', | ||
| 'returning to the horizon' | ||
| ] | ||
| } | ||
| }; | ||
| const patchGrammarCheck = G ? G.validatePatchGrammar(rawPatch) : { valid: true, errors: [] }; | ||
| const compiledPatch = G && patchGrammarCheck.valid ? G.compilePatchRecord(rawPatch) : null; | ||
| const patchGrammarDigest = sha256Hex(canonicalJSON(rawPatch)); | ||
|
|
||
| // 2. C3: Route graph & deterministic replay | ||
| const routeHistory = Array.isArray(input.routeHistory) && input.routeHistory.length > 0 | ||
| ? input.routeHistory | ||
| : ['040915', '041015', '042115', '040915']; | ||
| const routeGraph = G ? G.buildRouteGraph(version, seed) : null; | ||
| const routeReplay = G ? G.replayRouteHistory(version, seed, routeHistory) : null; | ||
| const routeGraphDigest = sha256Hex(canonicalJSON(routeGraph || { version, seed })); | ||
|
|
||
| // 3. C3: Relational perceptual field | ||
| const encounterState = { | ||
| chamber, | ||
| background: input.background || rawPatch.palette?.background || '#000000', | ||
| colors: input.colors || [rawPatch.palette?.foreground || '#00ffff', '#ff00ff', '#ffff00'], | ||
| dwellTime: typeof input.dwellTime === 'number' ? input.dwellTime : 3000, | ||
| visit: typeof input.visit === 'number' ? input.visit : 1, | ||
| history: routeHistory.map(ch => (typeof ch === 'object' ? ch : { chamber: ch })) | ||
| }; | ||
| const perceptualField = G ? G.evaluatePerceptualField(encounterState, input.viewport || { width: 1440, height: 1000 }) : null; | ||
|
|
||
| // 4. C3 & C4: Spatial poetry layer & reduced motion parity | ||
| const spatialOptions = Object.assign({ | ||
| width: 1200, | ||
| height: 800, | ||
| background: encounterState.background, | ||
| color: encounterState.colors[0], | ||
| minContrast: 4.5 | ||
| }, input.spatialOptions || {}); | ||
|
|
||
| const spatialPoetryLayer = G ? G.composeSpatialPoetryLayer(routeHistory, compiledPatch, spatialOptions) : null; | ||
| const reducedMotionLayer = SA && spatialPoetryLayer | ||
| ? SA.verifyReducedMotionParity(spatialPoetryLayer, { prefersReducedMotion: true, transitionMode: input.transitionMode || 'crossfade' }) | ||
| : null; | ||
|
|
||
| // 5. C4: Timed caption track & tactile envelope & cross-modal audit | ||
| const timedTextNodes = input.timedTextNodes || (spatialPoetryLayer?.nodes ? spatialPoetryLayer.nodes.map((n, i) => ({ | ||
| id: n.id, | ||
| text: n.text, | ||
| startTime: i * 1.5, | ||
| nodeId: n.id | ||
| })) : [ | ||
| { id: 'c-1', text: 'where i end and u begin', startTime: 0 }, | ||
| { id: 'c-2', text: 'through the six encounters of the loop', startTime: 2.2 }, | ||
| { id: 'c-3', text: 'returning to the horizon', startTime: 4.4 } | ||
| ]); | ||
|
|
||
| const captionTrack = SA ? SA.compileCaptionTrack(routeHistory, timedTextNodes, input.captionOptions || { | ||
| charsPerSecond: 18, | ||
| minDurationSec: 1.2 | ||
| }) : null; | ||
|
|
||
| const audioEvents = input.audioEvents || (captionTrack ? captionTrack.cues.map(c => ({ | ||
| id: `pulse-${c.id}`, | ||
| timeMs: c.startTimeMs, | ||
| durationMs: Math.min(250, Math.round(c.duration * 200)), | ||
| frequency: chamber === '040915' ? 32 : (chamber === '041015' ? 48 : 64), | ||
| intensity: 0.75 | ||
| })) : []); | ||
|
|
||
| const tactileEnvelope = SA ? SA.projectTactileSubBassEnvelope(encounterState, audioEvents, input.tactileOptions || { | ||
| peakLimit: 0.85, | ||
| rmsLimit: 0.707 | ||
| }) : null; | ||
|
|
||
| const sensoryAudit = SA && captionTrack && tactileEnvelope && reducedMotionLayer | ||
| ? SA.auditSensoryEquivalence(captionTrack, tactileEnvelope, reducedMotionLayer, { toleranceMs: input.syncToleranceMs || 50 }) | ||
| : { equivalent: false, violations: ['sensory audit incomplete'], maxDeltaMs: Infinity, summary: 'Sensory audit incomplete' }; | ||
|
|
||
| // 6. C3: Studio review session & deterministic review bundle | ||
| let reviewBundle = null; | ||
| if (G && typeof G.createStudioReviewSession === 'function') { | ||
| const reviewSession = G.createStudioReviewSession({ chamber, seed, background: encounterState.background }); | ||
| reviewSession.annotateEncounter(chamber, 'Wave 5 final publication baseline verification'); | ||
| if (rawPatch && patchGrammarCheck.valid) { | ||
| reviewSession.applyPreviewPatch(rawPatch); | ||
| } | ||
| reviewBundle = reviewSession.exportReviewBundle(); | ||
| } | ||
|
|
||
| // 7. C2: Documentation health | ||
| let runbookTarget = input.runbookPath; | ||
| if (!runbookTarget && typeof __dirname !== 'undefined') { | ||
| try { | ||
| const fs = require('node:fs'); | ||
| const path = require('node:path'); | ||
| const cand1 = path.resolve(__dirname, '../../docs/OPERATOR_RUNBOOK.md'); | ||
| const cand2 = path.resolve(__dirname, '../../../docs/OPERATOR_RUNBOOK.md'); | ||
| if (fs.existsSync(cand1)) runbookTarget = cand1; | ||
| else if (fs.existsSync(cand2)) runbookTarget = cand2; | ||
| } catch (_) {} | ||
| } | ||
| if (!runbookTarget) { | ||
| runbookTarget = input.runbookText || REQUIRED_RUNBOOK_SECTIONS.map(s => `# ${s}\nContent`).join('\n\n'); | ||
| } | ||
| const documentationHealth = verifyDocumentationHealth(runbookTarget); | ||
|
|
||
| // 8. C2: Release gates & signed release manifest | ||
| const metrics = Object.assign({ | ||
| frameTimeMs: 14.2, | ||
| memoryMb: 120, | ||
| reducedMotionSupported: true, | ||
| contrastRatio: perceptualField?.colorContrast?.min || 7.0, | ||
|
Comment on lines
+466
to
+470
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.
When AGENTS.md reference: AGENTS.md:L19-L19 Useful? React with 👍 / 👎. |
||
| ariaLandmarksComplete: true, | ||
| trajectoryDigestMatch: true | ||
| }, input.metrics || {}); | ||
|
|
||
| const releaseGates = evaluateReleaseGates(metrics); | ||
|
|
||
| const manifestInput = { | ||
| version, | ||
| commit, | ||
| routeGraphDigest, | ||
| patchGrammarDigest, | ||
| rollbackTarget: input.rollbackTarget || '1.1.0' | ||
| }; | ||
| const manifest = buildReleaseManifest(manifestInput); | ||
|
|
||
| // 9. Verification & Deterministic Package Digest | ||
| const ready = Boolean( | ||
| releaseGates.passed && | ||
| sensoryAudit.equivalent && | ||
| documentationHealth.healthy && | ||
| patchGrammarCheck.valid && | ||
| manifest.manifestDigest && | ||
| (routeReplay ? Boolean(routeReplay.trajectoryDigest) : true) | ||
| ); | ||
|
|
||
| const packagePayload = { | ||
| schema: 'hole-loop.six-encounters.package.v1', | ||
| version, | ||
| commit, | ||
| ready, | ||
| manifest, | ||
| sensoryAudit, | ||
| documentationHealth, | ||
| releaseGates, | ||
| artifacts: { | ||
| routeGraphDigest, | ||
| patchGrammarDigest, | ||
| trajectoryDigest: routeReplay?.trajectoryDigest || null, | ||
| reviewBundleDigest: reviewBundle?.digest || null | ||
| }, | ||
| publishedAt: input.publishedAt || '2026-10-25T12:00:00.000Z' | ||
| }; | ||
|
|
||
| const packageDigest = sha256Hex(canonicalJSON(packagePayload)); | ||
|
|
||
| return freeze({ | ||
| ...packagePayload, | ||
| packageDigest, | ||
| subsystems: { | ||
|
Comment on lines
+514
to
+519
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.
The digest is computed before Useful? React with 👍 / 👎. |
||
| routeGraph, | ||
| routeReplay, | ||
| perceptualField, | ||
| spatialPoetryLayer, | ||
| reducedMotionLayer, | ||
| captionTrack, | ||
| tactileEnvelope, | ||
| reviewBundle, | ||
| compiledPatch | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| root.HLReleaseGates = { | ||
| evaluateReleaseGates, | ||
| buildReleaseManifest, | ||
| verifyRollbackTarget, | ||
| verifyDocumentationHealth, | ||
| assembleSixEncountersPackage, | ||
| REQUIRED_RUNBOOK_SECTIONS, | ||
| canonicalJSON, | ||
| sha256Hex | ||
| }; | ||
|
|
||
| if (root.HLGeneration) { | ||
| root.HLGeneration.assembleSixEncountersPackage = assembleSixEncountersPackage; | ||
| } | ||
|
|
||
| if (typeof module !== 'undefined') { | ||
| module.exports = root.HLReleaseGates; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a caller supplies
runbookTextin this repository, automatic discovery selectsdocs/OPERATOR_RUNBOOK.mdfirst, so evenrunbookText: '# Incomplete\nNo procedures'returns healthy documentation and a ready package. Conversely, when no file or text is available, the fallback invents all required headings and passes the check anyway. Prioritize explicitly supplied documentation and fail the documentation gate when no real runbook is available, rather than qualifying an unrelated file or generated placeholder.Useful? React with 👍 / 👎.