diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 3a25cf8840..544da077db 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -4,16 +4,30 @@ Web Console preserves compatible API paths, schema and resource names, action na Visible branding, product-owned assets, icon identifiers, package metadata, and operator documentation use PastureStack. Historical identifiers remain only where they are server data or protocol contracts and must not be mechanically replaced. -Candidate `1.6.174` changes VM image guidance and defaults only: it does not +Candidate `1.6.175` refreshes only the image form's locally owned validation +aggregate after image/locale correction. Other validation errors and backend +save failures are preserved; shared NewOrEdit ownership, hook/finalizer behavior, +wire payloads, schema and authorization remain unchanged. Four new real-component +tests passed within 17/17 local Chrome 153 related tests. Exact-source official +CI, signed publication and packaged acceptance remain pending. No dependency +version or graph change is introduced; the full matrix remains INCOMPLETE. +See the [candidate release note](docs/releases/web-console-1.6.175.md). + +Published `1.6.174` changes VM image guidance and defaults only: it does not implement a VM runtime or image whitelist. Existing/custom/last-used VM images, ordinary container defaults and required validation remain supported. Shared image-required errors are localized in English and Traditional Chinese, with the existing English fallback for other locales. Backend capabilities, payloads, permissions and authentication contracts are unchanged; dependency -versions and the graph are unchanged. The five added Ember regressions and -formal release gates remain pending; 817 is only an expected test count. -Historical releases and HOLDs are not promoted, and the full matrix remains -INCOMPLETE. See the [candidate release note](docs/releases/web-console-1.6.174.md). +versions and the graph are unchanged. Exact-source CI37152802665 passed +817/817 tests with zero failures, skips or todo. Signed source +`d24b7f4e164f058e3ef9057347caa2080e2b5407`, both CI builds and anonymous public +downloads match archive SHA256 +`6408775898f412e4b27092eeddd9cdc2028ad7b27835f489139c2d0cf62c6776`. +Server512 packaging and QA512 image-form-only acceptance are verified separately; +VM boot is not verified and the parent validation-message defect remains pending +the unpublished Web175 candidate. Historical HOLDs are not promoted. The full +matrix remains INCOMPLETE. See the [release note](docs/releases/web-console-1.6.174.md). Published `1.6.173` reads native ProjectTemplate card labels from the model's existing `name`, not `localizedName`, which native ProjectTemplate does not implement. diff --git a/README.md b/README.md index 5ba5834a82..033f666e9c 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,37 @@ PastureStack is an independent community effort to preserve, audit, and moderniz ## Project status -Candidate `1.6.174` removes the ordinary-container default and quick picks from +Candidate `1.6.175` keeps the container/VM form's image validation message in +sync when the operator corrects the image or changes the locale. Only the +form's own validation aggregate is refreshed: model/command errors and later +backend save errors remain intact. The shared save lifecycle, lock ownership, +payloads, permissions and authentication are unchanged. Local Chrome 153 +verification passed 17/17 related tests with zero failures, skips or todo, +including four new real-component regression tests. This is source validation, +not packaged QA acceptance. Formal CI, publication and Server packaging for +this candidate remain pending; the full matrix remains INCOMPLETE. See the +[candidate release note](docs/releases/web-console-1.6.175.md). + +Published `1.6.174` removes the ordinary-container default and quick picks from the VM image field. Custom images, existing image values and the last-used VM image remain supported; ordinary container defaults are unchanged. A blank image still blocks the existing save lifecycle. VM boot-image guidance and required errors for both VM and container images use reviewed English and Traditional Chinese copy with the existing English fallback. -Dependency versions and the dependency graph are unchanged. Five added Ember -regressions have not yet run; 817 total tests is an expectation, not a result. -Exact-source CI, reproducible artifacts, signed publication, Server packaging -and packaged VM acceptance remain pending. Historical releases and HOLDs are -unchanged; the full matrix remains INCOMPLETE. See the -[candidate release note](docs/releases/web-console-1.6.174.md). +Dependency versions and the dependency graph are unchanged. Exact-source +[CI37152802665](https://github.com/PastureStack/web-console/actions/runs/37152802665) +passed 817/817 tests with zero failures, skips or todo. The signed numeric +[release](https://github.com/PastureStack/web-console/releases/tag/1.6.174) +pins source `d24b7f4e164f058e3ef9057347caa2080e2b5407`; both reproducible CI +archives and anonymous public readback match SHA256 +`6408775898f412e4b27092eeddd9cdc2028ad7b27835f489139c2d0cf62c6776` +(2,982,022 bytes). Server `v1.6.512` packages this exact component. QA125/8080 +fresh VM/container image forms passed eight scoped English/Traditional Chinese +cases with zero resource writes. This is not a successful VM boot or full +permission/resource/locale acceptance; the discovered stale parent image error +is addressed by the unpublished `1.6.175` candidate above. Historical releases +and HOLDs are unchanged; the full matrix remains INCOMPLETE. See the +[published release note](docs/releases/web-console-1.6.174.md). Published `1.6.173` repairs empty environment-template choice labels. These choices are native `ProjectTemplate` resources, not Catalog templates: their diff --git a/app/components/new-container/component.js b/app/components/new-container/component.js index e9c674ef3f..e106e5dfc8 100644 --- a/app/components/new-container/component.js +++ b/app/components/new-container/component.js @@ -462,7 +462,7 @@ export default Component.extend(NewOrEdit, SelectTab, { // ---------------------------------- validate() { this._super(); - var errors = this.get('errors')||[]; + var errors = (this.get('errors')||[]).slice(); if ( this.get('isService') ) { @@ -490,7 +490,6 @@ export default Component.extend(NewOrEdit, SelectTab, { hardwareIssues(config, host).forEach((key) => errors.push(this.get('intl').t(key === 'deviceGroup' ? 'formResources.deviceGroupHelp' : `formResources.errors.${key}`))); }); errors.pushObjects(this.get('scaleErrors')||[]); - errors.pushObjects(this.get('imageErrors')||[]); errors.pushObjects(this.get('portErrors')||[]); errors.pushObjects(this.get('diskErrors')||[]); @@ -502,7 +501,10 @@ export default Component.extend(NewOrEdit, SelectTab, { errors.push(this.get('intl').t('formPorts.preflight.error.sidekickBlocked')); } + this._nonImageValidationErrors = errors.slice(); + errors.pushObjects(this.get('imageErrors')||[]); errors = errors.uniq(); + this._imageValidationAggregate = errors.get('length') ? errors : null; if ( errors.get('length') ) { @@ -514,6 +516,19 @@ export default Component.extend(NewOrEdit, SelectTab, { return true; }, + // Refresh only the local aggregate produced by validate(), not a later save error. + imageErrorsDidChange: function() { + if ( !this._nonImageValidationErrors || this.get('errors') !== this._imageValidationAggregate ) { + return; + } + + let errors = this._nonImageValidationErrors.slice(); + errors.pushObjects(this.get('imageErrors')||[]); + errors = errors.uniq(); + this._imageValidationAggregate = errors.get('length') ? errors : null; + this.set('errors', this._imageValidationAggregate); + }.observes('imageErrors.[]'), + doSave() { if ( this.get('isService') && this.get('isUpgrade') ) { diff --git a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json index 42faa1ed01..e9ea2e5e99 100644 --- a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json +++ b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json @@ -1,12 +1,12 @@ { "name": "@pasturestack/web-console", - "version": "1.6.174", + "version": "1.6.175", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pasturestack/web-console", - "version": "1.6.174", + "version": "1.6.175", "license": "Apache-2.0", "dependencies": { "sass": "1.103.1" diff --git a/docs/releases/web-console-1.6.174.md b/docs/releases/web-console-1.6.174.md index f9f6eebf68..1befbc3736 100644 --- a/docs/releases/web-console-1.6.174.md +++ b/docs/releases/web-console-1.6.174.md @@ -1,8 +1,16 @@ # Web Console 1.6.174 -Candidate only. Exact-source CI, reproducible production artifacts, signed -publication, anonymous public readback and Server packaging have not yet been -completed for this version. No VM lifecycle PASS is claimed. +Published immutable numeric release. Exact-source CI37152802665 passed 817/817 +tests with zero failures, skips or todo, including the five direct VM-image +regressions. Signed source `d24b7f4e164f058e3ef9057347caa2080e2b5407` and both +reproducible CI archives match the anonymous public archive readback: SHA256 +`6408775898f412e4b27092eeddd9cdc2028ad7b27835f489139c2d0cf62c6776`, +2,982,022 bytes. Publication reused the CI asset without rebuilding. +Server `v1.6.512` packages this component; immutable image digest +`sha256:805078de83c0320c751dff90304bd841b8e64bec720079198258d22fa41d0145`. +QA125/8080 image-form-only acceptance passed eight English/Traditional Chinese +VM/container cases, with fresh dual MFA, three Full17/14 guards and zero +resource writes. No successful VM boot or full-matrix PASS is claimed. ## Root cause and bounded repair @@ -35,16 +43,16 @@ change only their root package version metadata to `1.6.174`. Five new real-component Ember regressions cover existing and last-used images, blank-image save cancellation, custom VM input without misleading quick picks, VM guidance/required-error locale changes and ordinary-container required-error -locale changes on the same form. They have not yet been executed. The expected -full test count is 817 (the previous 812 plus these five), not a verified result. +locale changes on the same form. All five passed in the verified 817-test CI run. Offline method controls and syntax checks do not replace Chrome rendering, exact-source CI or packaged native VM acceptance. -No source commit, formal run, archive checksum or publication coordinate is -asserted for this candidate. Reuse only the eventual reviewed exact-source CI -artifact for publication; do not rebuild or overwrite an older release. +The published coordinates above do not establish VM runtime acceptance. +The packaged forms exposed a separate stale parent image-validation error after +the child input was corrected. That observation remains OPEN on Server512; +the unpublished Web175 source candidate has four new focused regression tests. The existing vendor-pending advisory and audit policy remain unchanged; this -candidate is not a zero-CVE claim. +release is not a zero-CVE claim. The [published Web173 record](web-console-1.6.173.md) and its scoped results remain historical evidence, not proof of this candidate's VM lifecycle. diff --git a/docs/releases/web-console-1.6.175.md b/docs/releases/web-console-1.6.175.md new file mode 100644 index 0000000000..0b02d66b47 --- /dev/null +++ b/docs/releases/web-console-1.6.175.md @@ -0,0 +1,32 @@ +# Web Console 1.6.175 + +Candidate only. Formal exact-source CI, signed numeric release, reproducible +archive and Server packaging are pending. Local tests do not establish packaged +UI acceptance or full permission/resource/locale acceptance. + +## Root cause and minimal repair + +The child image form refreshed its own `imageErrors` when an operator corrected +the image, but `new-container` retained the previous required message in its +parent error aggregate. The same stale copy could remain after a locale change. + +Keep a private snapshot of non-image validation errors and observe the existing +image-error array. Refresh the parent only while it still owns the exact aggregate +it created. A later backend error replaces that array and is not overwritten. +The non-image snapshot preserves model and command errors, even when their text +matches the image error. No shared NewOrEdit change, new save hook, wire-format +change, authorization change or backend patch is introduced. + +Four new real-component QUnit tests cover VM/container correction, preservation +of non-image errors, locale replacement and a real save failure/errorSaving hook +followed by image correction. Local Chrome 153 passed 17/17 related tests with +zero failures, skips or todo. The full CI count is not yet a verified result. + +Dependency versions and the dependency graph are unchanged. Package metadata +changes only the root version. Existing session generation, mutex, session-bound +logout, OIDC, TOTP, Passkey, permissions, hardware payload and save ownership +remain intact. The full matrix is INCOMPLETE; historical HOLDs are preserved. +Executable version gates and the reviewed lockfile baseline carry the same +1.6.175 root metadata; no previous-release version pin is reused for this build. +Keep configuration, volumes and rollback artifacts. No company deployment is +authorized by this source change. diff --git a/package-lock.json b/package-lock.json index 42faa1ed01..e9ea2e5e99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pasturestack/web-console", - "version": "1.6.174", + "version": "1.6.175", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pasturestack/web-console", - "version": "1.6.174", + "version": "1.6.175", "license": "Apache-2.0", "dependencies": { "sass": "1.103.1" diff --git a/package.json b/package.json index 12c62eee76..2238e09614 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pasturestack/web-console", - "version": "1.6.174", + "version": "1.6.175", "private": true, "description": "PastureStack browser console for the compatible control platform.", "repository": { diff --git a/scripts/check-modernization-blockers b/scripts/check-modernization-blockers index 3b8fba134a..49935e6677 100755 --- a/scripts/check-modernization-blockers +++ b/scripts/check-modernization-blockers @@ -41,8 +41,8 @@ with open('package.json', encoding='utf-8') as f: print(json.load(f).get('version', '')) PY ) -if [[ "$version" != "1.6.174" ]]; then - echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.174" +if [[ "$version" != "1.6.175" ]]; then + echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.175" failures=$((failures + 1)) fi diff --git a/scripts/check-ui-console-workspace b/scripts/check-ui-console-workspace index f3a6ca34f2..f767b59139 100755 --- a/scripts/check-ui-console-workspace +++ b/scripts/check-ui-console-workspace @@ -141,4 +141,4 @@ if [[ -n ${PASTURESTACK_PRIVATE_MARKER:-} ]] && grep -RInF -- "$PASTURESTACK_PRI fi printf 'UI_CONSOLE_WORKSPACE_OK version=%s persistence=%s cross_tab=%s\n' \ - 1.6.174 browser-session broker-broadcast + 1.6.175 browser-session broker-broadcast diff --git a/scripts/check-ui-critical-high-dependencies b/scripts/check-ui-critical-high-dependencies index f138722f6e..a72e8b835c 100755 --- a/scripts/check-ui-critical-high-dependencies +++ b/scripts/check-ui-critical-high-dependencies @@ -70,7 +70,7 @@ if lock_bytes != baseline_bytes: lock = json.loads(lock_bytes) packages = lock.get("packages", {}) root = packages.get("", {}) -if package.get("version") != "1.6.174": +if package.get("version") != "1.6.175": fail(f"unexpected Web Console version: {package.get('version')}") if root.get("version") != package.get("version"): fail(f"lock root version differs: {root.get('version')}") diff --git a/tests/unit/components/new-container-image-errors-test.js b/tests/unit/components/new-container-image-errors-test.js new file mode 100644 index 0000000000..a3d315e244 --- /dev/null +++ b/tests/unit/components/new-container-image-errors-test.js @@ -0,0 +1,99 @@ +import { A } from '@ember/array'; +import EmberObject from '@ember/object'; +import { run } from '@ember/runloop'; +import { module, test } from 'qunit'; +import NewContainerComponent from 'ui/components/new-container/component'; +import FormImageComponent from 'ui/components/form-image/component'; +import inertRenderer from '../../helpers/inert-renderer'; +import { createOwned, destroyOwned } from '../../helpers/owned-subject'; + +module('Unit | Component | new container image errors'); + +function pair(isVm = true) { + let launchConfig = EmberObject.create({labels: {}, ports: A(), secrets: A()}); + let model = EmberObject.create({ + launchConfig, secondaryLaunchConfigs: A(), + validationErrors() { return A(); }, + }); + let parent, image; + let intl = EmberObject.create({label: '', t(key) { return this.get('label') + key; }}); + run(() => { + parent = createOwned(NewContainerComponent, { + renderer: inertRenderer(), intl, launchConfig, service: model, + primaryResource: model, primaryService: model, isService: true, + }, 'component'); + image = createOwned(FormImageComponent, { + renderer: inertRenderer(), intl, isVm, initialValue: 'docker:example.test/custom:qa', + projects: EmberObject.create({current: EmberObject.create({isWindows: false})}), + settings: EmberObject.create({appName: 'PastureStack'}), sendAction() {}, + }, 'component'); + }); + let sync = () => parent.set('imageErrors', image.get('errors')); + image.addObserver('errors', parent, sync); + run(sync); + return {parent, image, model, intl, destroy() { + image.removeObserver('errors', parent, sync); + destroyOwned(image); + destroyOwned(parent); + }}; +} + +test('VM and container correction clears only their locally aggregated required error', function(assert) { + for (let isVm of [true, false]) { + let p = pair(isVm); + try { + run(() => p.image.set('userInput', '')); + assert.notOk(p.parent.get('errors.length'), 'does not auto-validate the parent before local validation'); + assert.notOk(p.parent.validate()); + assert.strictEqual(p.parent.get('errors.length'), 1); + run(() => p.image.set('userInput', 'registry.example/custom:qa')); + assert.deepEqual(p.image.get('errors'), []); + assert.strictEqual(p.parent.get('errors'), null, 'top-errors clears without another validate/save'); + assert.ok(p.parent.validate(), 'required validation is not weakened'); + } finally { p.destroy(); } + } +}); + +test('correction retains model and other component errors, including matching text', function(assert) { + let p = pair(); + let required = 'formImage.vm.bootImageRequired'; + try { + p.model.validationErrors = () => A(['model error']); + run(() => { + p.parent.set('commandErrors', A([required, 'command error'])); + p.image.set('userInput', ''); + }); + assert.notOk(p.parent.validate()); + run(() => p.image.set('userInput', 'registry.example/custom:qa')); + assert.deepEqual(p.parent.get('errors'), ['model error', required, 'command error'], 'matching non-image diagnostics are not removed'); + assert.notOk(p.parent.validate(), 'remaining invalid fields still block saving'); + } finally { p.destroy(); } +}); + +test('locale replacement refreshes the required text without retaining the previous image message', function(assert) { + let p = pair(); + try { + run(() => p.image.set('userInput', '')); + assert.notOk(p.parent.validate()); + run(() => { p.intl.set('label', 'zh:'); p.image.validate(); }); + assert.deepEqual(p.parent.get('errors'), ['zh:formImage.vm.bootImageRequired']); + run(() => p.image.set('userInput', 'registry.example/custom:qa')); + assert.strictEqual(p.parent.get('errors'), null); + } finally { p.destroy(); } +}); + +test('a real save failure and errorSaving hook survive subsequent image correction', async function(assert) { + let p = pair(); + let cleanupCalls = 0; + p.parent.errorSaving = () => { cleanupCalls++; }; + try { + run(() => p.image.set('userInput', '')); + assert.notOk(p.parent.validate()); + await p.parent._handleSaveFailure('backend rejection'); + let backend = p.parent.get('errors'); + assert.deepEqual(backend, ['backend rejection']); + run(() => p.image.set('userInput', 'registry.example/custom:qa')); + assert.strictEqual(p.parent.get('errors'), backend, 'the server error array is neither cleared nor replaced'); + assert.strictEqual(cleanupCalls, 1, 'the existing failure cleanup runs once'); + } finally { p.destroy(); } +});