v0.1.0 release hardening plan #1

Description

@zekageri

Goal

Make Phase safe and verifiable for the first public v0.1.0 release.

This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

Release blockers

1. Make task teardown ownership race-free

The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

  • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
  • Prefer an explicit worker-exit handshake:
    • worker completes shutdown and final diagnostics
    • worker signals exitReady
    • worker suspends or waits without touching PhaseImpl
    • caller-side end() deletes the worker task using the stored handle
    • only then clear the handle and mark the object ended
  • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
  • Ensure task memory is released exactly once.
  • Ensure semaphore/mutex destruction only happens after worker termination.
  • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
  • Add a repeated create/init/start/end/destroy stress test.

2. Reject end() from the Phase task

Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

  • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
  • Return a clear non-success result without blocking.
  • Document that end() must be called by another task.
  • Define destructor behavior when destruction is attempted from the Phase task.
  • Add tests covering end() from every callback type.
  • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

3. Fix callback string lifetime guarantees

PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

  • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
  • Review nodeName and message for the same guarantee.
  • Keep callbacks outside the internal mutex.
  • Add a concurrency test where another task changes pause state while onChange is executing.
  • Keep the documented guarantee that pointers are valid for the duration of the callback.

4. Remove lifecycle-time dynamic allocations

Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

  • Make the registered graph immutable after registration closes.
  • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
  • Avoid copying dependency vectors during lifecycle evaluation.
  • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
  • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
  • Ensure no lifecycle path can terminate because a snapshot allocation failed.
  • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
  • Add an allocation audit/test around start, group polling, stop, rollback, and end.

5. Add behavioral test coverage

Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

  • Add deterministic host tests or an ESP32 test harness for:
    • dependency-ordered init
    • dependency-ordered start/readiness
    • reverse stop order
    • reverse deinit order
    • required init failure rollback
    • required start failure rollback
    • optional init failure
    • optional start failure and deinit cleanup
    • optional dependent skipping
    • missing dependency validation
    • circular dependency validation
    • group success
    • group timeout
    • pause/resume during group polling
    • stop immediately after start()
    • stop after the worker consumes the start request
    • repeated stop requests
    • restart after Stopped
    • concurrent diagnostics/state reads
    • callback-triggered stop/pause/resume
    • callback-triggered end() rejection
    • teardown synchronization
  • Make behavioral tests blocking in CI.
  • Keep multi-board example builds as compatibility checks.

6. Align release metadata with the tag

  • Set library.json version to 0.1.0.
  • Set library.properties version to 0.1.0.
  • Update README status from 0.0.1 to 0.1.0.
  • Add a blocking CI check that vX.Y.Z matches both manifests.
  • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

Correctness and observability fixes

7. Correct stack high-water-mark units

ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

  • Return the value without multiplying it.
  • Add a focused test or platform assertion for the diagnostic unit.
  • Confirm documentation says bytes.

8. Define shutdown failure semantics

Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

Choose and document one policy:

  • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

or

  • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

Also:

  • Preserve cleanup progress even when one stop/deinit callback fails.
  • Add tests with multiple failing shutdown callbacks.

API and documentation pass

  • Document exact async semantics of start(), stop(), pause(), and resume().
  • Document allowed state transitions and restart behavior after Stopped.
  • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
  • Document callback execution context and reentrancy rules.
  • Document end() as terminal and external-task-only.
  • Document allocation behavior after runtime-allocation changes.
  • Verify every example checks registration builder results where practical.
  • Add a release changelog summarizing the supported API and known cooperative limitations.

Recommended implementation order

  1. Teardown ownership and worker-exit handshake.
  2. Self-end()/destructor policy.
  3. Callback pointer lifetime fixes.
  4. Runtime snapshot/allocation redesign.
  5. Behavioral test harness and race tests.
  6. Stack diagnostic and shutdown-result fixes.
  7. Metadata, documentation, and release validation.

Release acceptance criteria

v0.1.0 is ready when:

  • No task can access PhaseImpl after end() returns.
  • end() cannot deadlock when invoked from the Phase task.
  • All PhaseChange pointers remain valid throughout callback execution.
  • Lifecycle execution does not depend on unhandled dynamic allocations.
  • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
  • CI is green for behavioral tests and all supported board builds.
  • Release tag and manifest versions match 0.1.0.
  • Documentation matches the implemented lifecycle and shutdown semantics.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      v0.1.0 release hardening plan #1

      Description

      @zekageri

      Goal

      Make Phase safe and verifiable for the first public v0.1.0 release.

      This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

      Release blockers

      1. Make task teardown ownership race-free

      The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

      • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
      • Prefer an explicit worker-exit handshake:
        • worker completes shutdown and final diagnostics
        • worker signals exitReady
        • worker suspends or waits without touching PhaseImpl
        • caller-side end() deletes the worker task using the stored handle
        • only then clear the handle and mark the object ended
      • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
      • Ensure task memory is released exactly once.
      • Ensure semaphore/mutex destruction only happens after worker termination.
      • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
      • Add a repeated create/init/start/end/destroy stress test.

      2. Reject end() from the Phase task

      Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

      • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
      • Return a clear non-success result without blocking.
      • Document that end() must be called by another task.
      • Define destructor behavior when destruction is attempted from the Phase task.
      • Add tests covering end() from every callback type.
      • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

      3. Fix callback string lifetime guarantees

      PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

      • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
      • Review nodeName and message for the same guarantee.
      • Keep callbacks outside the internal mutex.
      • Add a concurrency test where another task changes pause state while onChange is executing.
      • Keep the documented guarantee that pointers are valid for the duration of the callback.

      4. Remove lifecycle-time dynamic allocations

      Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

      • Make the registered graph immutable after registration closes.
      • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
      • Avoid copying dependency vectors during lifecycle evaluation.
      • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
      • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
      • Ensure no lifecycle path can terminate because a snapshot allocation failed.
      • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
      • Add an allocation audit/test around start, group polling, stop, rollback, and end.

      5. Add behavioral test coverage

      Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

      • Add deterministic host tests or an ESP32 test harness for:
        • dependency-ordered init
        • dependency-ordered start/readiness
        • reverse stop order
        • reverse deinit order
        • required init failure rollback
        • required start failure rollback
        • optional init failure
        • optional start failure and deinit cleanup
        • optional dependent skipping
        • missing dependency validation
        • circular dependency validation
        • group success
        • group timeout
        • pause/resume during group polling
        • stop immediately after start()
        • stop after the worker consumes the start request
        • repeated stop requests
        • restart after Stopped
        • concurrent diagnostics/state reads
        • callback-triggered stop/pause/resume
        • callback-triggered end() rejection
        • teardown synchronization
      • Make behavioral tests blocking in CI.
      • Keep multi-board example builds as compatibility checks.

      6. Align release metadata with the tag

      • Set library.json version to 0.1.0.
      • Set library.properties version to 0.1.0.
      • Update README status from 0.0.1 to 0.1.0.
      • Add a blocking CI check that vX.Y.Z matches both manifests.
      • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

      Correctness and observability fixes

      7. Correct stack high-water-mark units

      ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

      • Return the value without multiplying it.
      • Add a focused test or platform assertion for the diagnostic unit.
      • Confirm documentation says bytes.

      8. Define shutdown failure semantics

      Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

      Choose and document one policy:

      • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

      or

      • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

      Also:

      • Preserve cleanup progress even when one stop/deinit callback fails.
      • Add tests with multiple failing shutdown callbacks.

      API and documentation pass

      • Document exact async semantics of start(), stop(), pause(), and resume().
      • Document allowed state transitions and restart behavior after Stopped.
      • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
      • Document callback execution context and reentrancy rules.
      • Document end() as terminal and external-task-only.
      • Document allocation behavior after runtime-allocation changes.
      • Verify every example checks registration builder results where practical.
      • Add a release changelog summarizing the supported API and known cooperative limitations.

      Recommended implementation order

      1. Teardown ownership and worker-exit handshake.
      2. Self-end()/destructor policy.
      3. Callback pointer lifetime fixes.
      4. Runtime snapshot/allocation redesign.
      5. Behavioral test harness and race tests.
      6. Stack diagnostic and shutdown-result fixes.
      7. Metadata, documentation, and release validation.

      Release acceptance criteria

      v0.1.0 is ready when:

      • No task can access PhaseImpl after end() returns.
      • end() cannot deadlock when invoked from the Phase task.
      • All PhaseChange pointers remain valid throughout callback execution.
      • Lifecycle execution does not depend on unhandled dynamic allocations.
      • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
      • CI is green for behavioral tests and all supported board builds.
      • Release tag and manifest versions match 0.1.0.
      • Documentation matches the implemented lifecycle and shutdown semantics.

      Activity

      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          v0.1.0 release hardening plan #1

          Description

          @zekageri

          Goal

          Make Phase safe and verifiable for the first public v0.1.0 release.

          This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

          Release blockers

          1. Make task teardown ownership race-free

          The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

          • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
          • Prefer an explicit worker-exit handshake:
            • worker completes shutdown and final diagnostics
            • worker signals exitReady
            • worker suspends or waits without touching PhaseImpl
            • caller-side end() deletes the worker task using the stored handle
            • only then clear the handle and mark the object ended
          • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
          • Ensure task memory is released exactly once.
          • Ensure semaphore/mutex destruction only happens after worker termination.
          • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
          • Add a repeated create/init/start/end/destroy stress test.

          2. Reject end() from the Phase task

          Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

          • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
          • Return a clear non-success result without blocking.
          • Document that end() must be called by another task.
          • Define destructor behavior when destruction is attempted from the Phase task.
          • Add tests covering end() from every callback type.
          • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

          3. Fix callback string lifetime guarantees

          PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

          • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
          • Review nodeName and message for the same guarantee.
          • Keep callbacks outside the internal mutex.
          • Add a concurrency test where another task changes pause state while onChange is executing.
          • Keep the documented guarantee that pointers are valid for the duration of the callback.

          4. Remove lifecycle-time dynamic allocations

          Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

          • Make the registered graph immutable after registration closes.
          • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
          • Avoid copying dependency vectors during lifecycle evaluation.
          • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
          • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
          • Ensure no lifecycle path can terminate because a snapshot allocation failed.
          • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
          • Add an allocation audit/test around start, group polling, stop, rollback, and end.

          5. Add behavioral test coverage

          Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

          • Add deterministic host tests or an ESP32 test harness for:
            • dependency-ordered init
            • dependency-ordered start/readiness
            • reverse stop order
            • reverse deinit order
            • required init failure rollback
            • required start failure rollback
            • optional init failure
            • optional start failure and deinit cleanup
            • optional dependent skipping
            • missing dependency validation
            • circular dependency validation
            • group success
            • group timeout
            • pause/resume during group polling
            • stop immediately after start()
            • stop after the worker consumes the start request
            • repeated stop requests
            • restart after Stopped
            • concurrent diagnostics/state reads
            • callback-triggered stop/pause/resume
            • callback-triggered end() rejection
            • teardown synchronization
          • Make behavioral tests blocking in CI.
          • Keep multi-board example builds as compatibility checks.

          6. Align release metadata with the tag

          • Set library.json version to 0.1.0.
          • Set library.properties version to 0.1.0.
          • Update README status from 0.0.1 to 0.1.0.
          • Add a blocking CI check that vX.Y.Z matches both manifests.
          • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

          Correctness and observability fixes

          7. Correct stack high-water-mark units

          ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

          • Return the value without multiplying it.
          • Add a focused test or platform assertion for the diagnostic unit.
          • Confirm documentation says bytes.

          8. Define shutdown failure semantics

          Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

          Choose and document one policy:

          • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

          or

          • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

          Also:

          • Preserve cleanup progress even when one stop/deinit callback fails.
          • Add tests with multiple failing shutdown callbacks.

          API and documentation pass

          • Document exact async semantics of start(), stop(), pause(), and resume().
          • Document allowed state transitions and restart behavior after Stopped.
          • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
          • Document callback execution context and reentrancy rules.
          • Document end() as terminal and external-task-only.
          • Document allocation behavior after runtime-allocation changes.
          • Verify every example checks registration builder results where practical.
          • Add a release changelog summarizing the supported API and known cooperative limitations.

          Recommended implementation order

          1. Teardown ownership and worker-exit handshake.
          2. Self-end()/destructor policy.
          3. Callback pointer lifetime fixes.
          4. Runtime snapshot/allocation redesign.
          5. Behavioral test harness and race tests.
          6. Stack diagnostic and shutdown-result fixes.
          7. Metadata, documentation, and release validation.

          Release acceptance criteria

          v0.1.0 is ready when:

          • No task can access PhaseImpl after end() returns.
          • end() cannot deadlock when invoked from the Phase task.
          • All PhaseChange pointers remain valid throughout callback execution.
          • Lifecycle execution does not depend on unhandled dynamic allocations.
          • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
          • CI is green for behavioral tests and all supported board builds.
          • Release tag and manifest versions match 0.1.0.
          • Documentation matches the implemented lifecycle and shutdown semantics.

          Activity

          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              v0.1.0 release hardening plan #1

              Description

              @zekageri

              Goal

              Make Phase safe and verifiable for the first public v0.1.0 release.

              This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

              Release blockers

              1. Make task teardown ownership race-free

              The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

              • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
              • Prefer an explicit worker-exit handshake:
                • worker completes shutdown and final diagnostics
                • worker signals exitReady
                • worker suspends or waits without touching PhaseImpl
                • caller-side end() deletes the worker task using the stored handle
                • only then clear the handle and mark the object ended
              • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
              • Ensure task memory is released exactly once.
              • Ensure semaphore/mutex destruction only happens after worker termination.
              • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
              • Add a repeated create/init/start/end/destroy stress test.

              2. Reject end() from the Phase task

              Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

              • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
              • Return a clear non-success result without blocking.
              • Document that end() must be called by another task.
              • Define destructor behavior when destruction is attempted from the Phase task.
              • Add tests covering end() from every callback type.
              • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

              3. Fix callback string lifetime guarantees

              PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

              • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
              • Review nodeName and message for the same guarantee.
              • Keep callbacks outside the internal mutex.
              • Add a concurrency test where another task changes pause state while onChange is executing.
              • Keep the documented guarantee that pointers are valid for the duration of the callback.

              4. Remove lifecycle-time dynamic allocations

              Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

              • Make the registered graph immutable after registration closes.
              • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
              • Avoid copying dependency vectors during lifecycle evaluation.
              • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
              • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
              • Ensure no lifecycle path can terminate because a snapshot allocation failed.
              • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
              • Add an allocation audit/test around start, group polling, stop, rollback, and end.

              5. Add behavioral test coverage

              Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

              • Add deterministic host tests or an ESP32 test harness for:
                • dependency-ordered init
                • dependency-ordered start/readiness
                • reverse stop order
                • reverse deinit order
                • required init failure rollback
                • required start failure rollback
                • optional init failure
                • optional start failure and deinit cleanup
                • optional dependent skipping
                • missing dependency validation
                • circular dependency validation
                • group success
                • group timeout
                • pause/resume during group polling
                • stop immediately after start()
                • stop after the worker consumes the start request
                • repeated stop requests
                • restart after Stopped
                • concurrent diagnostics/state reads
                • callback-triggered stop/pause/resume
                • callback-triggered end() rejection
                • teardown synchronization
              • Make behavioral tests blocking in CI.
              • Keep multi-board example builds as compatibility checks.

              6. Align release metadata with the tag

              • Set library.json version to 0.1.0.
              • Set library.properties version to 0.1.0.
              • Update README status from 0.0.1 to 0.1.0.
              • Add a blocking CI check that vX.Y.Z matches both manifests.
              • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

              Correctness and observability fixes

              7. Correct stack high-water-mark units

              ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

              • Return the value without multiplying it.
              • Add a focused test or platform assertion for the diagnostic unit.
              • Confirm documentation says bytes.

              8. Define shutdown failure semantics

              Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

              Choose and document one policy:

              • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

              or

              • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

              Also:

              • Preserve cleanup progress even when one stop/deinit callback fails.
              • Add tests with multiple failing shutdown callbacks.

              API and documentation pass

              • Document exact async semantics of start(), stop(), pause(), and resume().
              • Document allowed state transitions and restart behavior after Stopped.
              • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
              • Document callback execution context and reentrancy rules.
              • Document end() as terminal and external-task-only.
              • Document allocation behavior after runtime-allocation changes.
              • Verify every example checks registration builder results where practical.
              • Add a release changelog summarizing the supported API and known cooperative limitations.

              Recommended implementation order

              1. Teardown ownership and worker-exit handshake.
              2. Self-end()/destructor policy.
              3. Callback pointer lifetime fixes.
              4. Runtime snapshot/allocation redesign.
              5. Behavioral test harness and race tests.
              6. Stack diagnostic and shutdown-result fixes.
              7. Metadata, documentation, and release validation.

              Release acceptance criteria

              v0.1.0 is ready when:

              • No task can access PhaseImpl after end() returns.
              • end() cannot deadlock when invoked from the Phase task.
              • All PhaseChange pointers remain valid throughout callback execution.
              • Lifecycle execution does not depend on unhandled dynamic allocations.
              • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
              • CI is green for behavioral tests and all supported board builds.
              • Release tag and manifest versions match 0.1.0.
              • Documentation matches the implemented lifecycle and shutdown semantics.

              Activity

              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  v0.1.0 release hardening plan #1

                  Description

                  @zekageri

                  Goal

                  Make Phase safe and verifiable for the first public v0.1.0 release.

                  This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

                  Release blockers

                  1. Make task teardown ownership race-free

                  The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

                  • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
                  • Prefer an explicit worker-exit handshake:
                    • worker completes shutdown and final diagnostics
                    • worker signals exitReady
                    • worker suspends or waits without touching PhaseImpl
                    • caller-side end() deletes the worker task using the stored handle
                    • only then clear the handle and mark the object ended
                  • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
                  • Ensure task memory is released exactly once.
                  • Ensure semaphore/mutex destruction only happens after worker termination.
                  • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
                  • Add a repeated create/init/start/end/destroy stress test.

                  2. Reject end() from the Phase task

                  Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

                  • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
                  • Return a clear non-success result without blocking.
                  • Document that end() must be called by another task.
                  • Define destructor behavior when destruction is attempted from the Phase task.
                  • Add tests covering end() from every callback type.
                  • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

                  3. Fix callback string lifetime guarantees

                  PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

                  • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
                  • Review nodeName and message for the same guarantee.
                  • Keep callbacks outside the internal mutex.
                  • Add a concurrency test where another task changes pause state while onChange is executing.
                  • Keep the documented guarantee that pointers are valid for the duration of the callback.

                  4. Remove lifecycle-time dynamic allocations

                  Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

                  • Make the registered graph immutable after registration closes.
                  • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
                  • Avoid copying dependency vectors during lifecycle evaluation.
                  • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
                  • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
                  • Ensure no lifecycle path can terminate because a snapshot allocation failed.
                  • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
                  • Add an allocation audit/test around start, group polling, stop, rollback, and end.

                  5. Add behavioral test coverage

                  Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

                  • Add deterministic host tests or an ESP32 test harness for:
                    • dependency-ordered init
                    • dependency-ordered start/readiness
                    • reverse stop order
                    • reverse deinit order
                    • required init failure rollback
                    • required start failure rollback
                    • optional init failure
                    • optional start failure and deinit cleanup
                    • optional dependent skipping
                    • missing dependency validation
                    • circular dependency validation
                    • group success
                    • group timeout
                    • pause/resume during group polling
                    • stop immediately after start()
                    • stop after the worker consumes the start request
                    • repeated stop requests
                    • restart after Stopped
                    • concurrent diagnostics/state reads
                    • callback-triggered stop/pause/resume
                    • callback-triggered end() rejection
                    • teardown synchronization
                  • Make behavioral tests blocking in CI.
                  • Keep multi-board example builds as compatibility checks.

                  6. Align release metadata with the tag

                  • Set library.json version to 0.1.0.
                  • Set library.properties version to 0.1.0.
                  • Update README status from 0.0.1 to 0.1.0.
                  • Add a blocking CI check that vX.Y.Z matches both manifests.
                  • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

                  Correctness and observability fixes

                  7. Correct stack high-water-mark units

                  ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

                  • Return the value without multiplying it.
                  • Add a focused test or platform assertion for the diagnostic unit.
                  • Confirm documentation says bytes.

                  8. Define shutdown failure semantics

                  Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

                  Choose and document one policy:

                  • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

                  or

                  • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

                  Also:

                  • Preserve cleanup progress even when one stop/deinit callback fails.
                  • Add tests with multiple failing shutdown callbacks.

                  API and documentation pass

                  • Document exact async semantics of start(), stop(), pause(), and resume().
                  • Document allowed state transitions and restart behavior after Stopped.
                  • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
                  • Document callback execution context and reentrancy rules.
                  • Document end() as terminal and external-task-only.
                  • Document allocation behavior after runtime-allocation changes.
                  • Verify every example checks registration builder results where practical.
                  • Add a release changelog summarizing the supported API and known cooperative limitations.

                  Recommended implementation order

                  1. Teardown ownership and worker-exit handshake.
                  2. Self-end()/destructor policy.
                  3. Callback pointer lifetime fixes.
                  4. Runtime snapshot/allocation redesign.
                  5. Behavioral test harness and race tests.
                  6. Stack diagnostic and shutdown-result fixes.
                  7. Metadata, documentation, and release validation.

                  Release acceptance criteria

                  v0.1.0 is ready when:

                  • No task can access PhaseImpl after end() returns.
                  • end() cannot deadlock when invoked from the Phase task.
                  • All PhaseChange pointers remain valid throughout callback execution.
                  • Lifecycle execution does not depend on unhandled dynamic allocations.
                  • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
                  • CI is green for behavioral tests and all supported board builds.
                  • Release tag and manifest versions match 0.1.0.
                  • Documentation matches the implemented lifecycle and shutdown semantics.

                  Activity

                  Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      v0.1.0 release hardening plan #1

                      Description

                      @zekageri

                      Goal

                      Make Phase safe and verifiable for the first public v0.1.0 release.

                      This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

                      Release blockers

                      1. Make task teardown ownership race-free

                      The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

                      • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
                      • Prefer an explicit worker-exit handshake:
                        • worker completes shutdown and final diagnostics
                        • worker signals exitReady
                        • worker suspends or waits without touching PhaseImpl
                        • caller-side end() deletes the worker task using the stored handle
                        • only then clear the handle and mark the object ended
                      • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
                      • Ensure task memory is released exactly once.
                      • Ensure semaphore/mutex destruction only happens after worker termination.
                      • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
                      • Add a repeated create/init/start/end/destroy stress test.

                      2. Reject end() from the Phase task

                      Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

                      • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
                      • Return a clear non-success result without blocking.
                      • Document that end() must be called by another task.
                      • Define destructor behavior when destruction is attempted from the Phase task.
                      • Add tests covering end() from every callback type.
                      • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

                      3. Fix callback string lifetime guarantees

                      PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

                      • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
                      • Review nodeName and message for the same guarantee.
                      • Keep callbacks outside the internal mutex.
                      • Add a concurrency test where another task changes pause state while onChange is executing.
                      • Keep the documented guarantee that pointers are valid for the duration of the callback.

                      4. Remove lifecycle-time dynamic allocations

                      Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

                      • Make the registered graph immutable after registration closes.
                      • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
                      • Avoid copying dependency vectors during lifecycle evaluation.
                      • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
                      • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
                      • Ensure no lifecycle path can terminate because a snapshot allocation failed.
                      • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
                      • Add an allocation audit/test around start, group polling, stop, rollback, and end.

                      5. Add behavioral test coverage

                      Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

                      • Add deterministic host tests or an ESP32 test harness for:
                        • dependency-ordered init
                        • dependency-ordered start/readiness
                        • reverse stop order
                        • reverse deinit order
                        • required init failure rollback
                        • required start failure rollback
                        • optional init failure
                        • optional start failure and deinit cleanup
                        • optional dependent skipping
                        • missing dependency validation
                        • circular dependency validation
                        • group success
                        • group timeout
                        • pause/resume during group polling
                        • stop immediately after start()
                        • stop after the worker consumes the start request
                        • repeated stop requests
                        • restart after Stopped
                        • concurrent diagnostics/state reads
                        • callback-triggered stop/pause/resume
                        • callback-triggered end() rejection
                        • teardown synchronization
                      • Make behavioral tests blocking in CI.
                      • Keep multi-board example builds as compatibility checks.

                      6. Align release metadata with the tag

                      • Set library.json version to 0.1.0.
                      • Set library.properties version to 0.1.0.
                      • Update README status from 0.0.1 to 0.1.0.
                      • Add a blocking CI check that vX.Y.Z matches both manifests.
                      • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

                      Correctness and observability fixes

                      7. Correct stack high-water-mark units

                      ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

                      • Return the value without multiplying it.
                      • Add a focused test or platform assertion for the diagnostic unit.
                      • Confirm documentation says bytes.

                      8. Define shutdown failure semantics

                      Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

                      Choose and document one policy:

                      • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

                      or

                      • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

                      Also:

                      • Preserve cleanup progress even when one stop/deinit callback fails.
                      • Add tests with multiple failing shutdown callbacks.

                      API and documentation pass

                      • Document exact async semantics of start(), stop(), pause(), and resume().
                      • Document allowed state transitions and restart behavior after Stopped.
                      • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
                      • Document callback execution context and reentrancy rules.
                      • Document end() as terminal and external-task-only.
                      • Document allocation behavior after runtime-allocation changes.
                      • Verify every example checks registration builder results where practical.
                      • Add a release changelog summarizing the supported API and known cooperative limitations.

                      Recommended implementation order

                      1. Teardown ownership and worker-exit handshake.
                      2. Self-end()/destructor policy.
                      3. Callback pointer lifetime fixes.
                      4. Runtime snapshot/allocation redesign.
                      5. Behavioral test harness and race tests.
                      6. Stack diagnostic and shutdown-result fixes.
                      7. Metadata, documentation, and release validation.

                      Release acceptance criteria

                      v0.1.0 is ready when:

                      • No task can access PhaseImpl after end() returns.
                      • end() cannot deadlock when invoked from the Phase task.
                      • All PhaseChange pointers remain valid throughout callback execution.
                      • Lifecycle execution does not depend on unhandled dynamic allocations.
                      • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
                      • CI is green for behavioral tests and all supported board builds.
                      • Release tag and manifest versions match 0.1.0.
                      • Documentation matches the implemented lifecycle and shutdown semantics.

                      Activity

                      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          v0.1.0 release hardening plan #1

                          Description

                          @zekageri

                          Goal

                          Make Phase safe and verifiable for the first public v0.1.0 release.

                          This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

                          Release blockers

                          1. Make task teardown ownership race-free

                          The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

                          • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
                          • Prefer an explicit worker-exit handshake:
                            • worker completes shutdown and final diagnostics
                            • worker signals exitReady
                            • worker suspends or waits without touching PhaseImpl
                            • caller-side end() deletes the worker task using the stored handle
                            • only then clear the handle and mark the object ended
                          • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
                          • Ensure task memory is released exactly once.
                          • Ensure semaphore/mutex destruction only happens after worker termination.
                          • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
                          • Add a repeated create/init/start/end/destroy stress test.

                          2. Reject end() from the Phase task

                          Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

                          • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
                          • Return a clear non-success result without blocking.
                          • Document that end() must be called by another task.
                          • Define destructor behavior when destruction is attempted from the Phase task.
                          • Add tests covering end() from every callback type.
                          • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

                          3. Fix callback string lifetime guarantees

                          PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

                          • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
                          • Review nodeName and message for the same guarantee.
                          • Keep callbacks outside the internal mutex.
                          • Add a concurrency test where another task changes pause state while onChange is executing.
                          • Keep the documented guarantee that pointers are valid for the duration of the callback.

                          4. Remove lifecycle-time dynamic allocations

                          Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

                          • Make the registered graph immutable after registration closes.
                          • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
                          • Avoid copying dependency vectors during lifecycle evaluation.
                          • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
                          • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
                          • Ensure no lifecycle path can terminate because a snapshot allocation failed.
                          • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
                          • Add an allocation audit/test around start, group polling, stop, rollback, and end.

                          5. Add behavioral test coverage

                          Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

                          • Add deterministic host tests or an ESP32 test harness for:
                            • dependency-ordered init
                            • dependency-ordered start/readiness
                            • reverse stop order
                            • reverse deinit order
                            • required init failure rollback
                            • required start failure rollback
                            • optional init failure
                            • optional start failure and deinit cleanup
                            • optional dependent skipping
                            • missing dependency validation
                            • circular dependency validation
                            • group success
                            • group timeout
                            • pause/resume during group polling
                            • stop immediately after start()
                            • stop after the worker consumes the start request
                            • repeated stop requests
                            • restart after Stopped
                            • concurrent diagnostics/state reads
                            • callback-triggered stop/pause/resume
                            • callback-triggered end() rejection
                            • teardown synchronization
                          • Make behavioral tests blocking in CI.
                          • Keep multi-board example builds as compatibility checks.

                          6. Align release metadata with the tag

                          • Set library.json version to 0.1.0.
                          • Set library.properties version to 0.1.0.
                          • Update README status from 0.0.1 to 0.1.0.
                          • Add a blocking CI check that vX.Y.Z matches both manifests.
                          • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

                          Correctness and observability fixes

                          7. Correct stack high-water-mark units

                          ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

                          • Return the value without multiplying it.
                          • Add a focused test or platform assertion for the diagnostic unit.
                          • Confirm documentation says bytes.

                          8. Define shutdown failure semantics

                          Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

                          Choose and document one policy:

                          • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

                          or

                          • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

                          Also:

                          • Preserve cleanup progress even when one stop/deinit callback fails.
                          • Add tests with multiple failing shutdown callbacks.

                          API and documentation pass

                          • Document exact async semantics of start(), stop(), pause(), and resume().
                          • Document allowed state transitions and restart behavior after Stopped.
                          • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
                          • Document callback execution context and reentrancy rules.
                          • Document end() as terminal and external-task-only.
                          • Document allocation behavior after runtime-allocation changes.
                          • Verify every example checks registration builder results where practical.
                          • Add a release changelog summarizing the supported API and known cooperative limitations.

                          Recommended implementation order

                          1. Teardown ownership and worker-exit handshake.
                          2. Self-end()/destructor policy.
                          3. Callback pointer lifetime fixes.
                          4. Runtime snapshot/allocation redesign.
                          5. Behavioral test harness and race tests.
                          6. Stack diagnostic and shutdown-result fixes.
                          7. Metadata, documentation, and release validation.

                          Release acceptance criteria

                          v0.1.0 is ready when:

                          • No task can access PhaseImpl after end() returns.
                          • end() cannot deadlock when invoked from the Phase task.
                          • All PhaseChange pointers remain valid throughout callback execution.
                          • Lifecycle execution does not depend on unhandled dynamic allocations.
                          • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
                          • CI is green for behavioral tests and all supported board builds.
                          • Release tag and manifest versions match 0.1.0.
                          • Documentation matches the implemented lifecycle and shutdown semantics.

                          Activity

                          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              v0.1.0 release hardening plan #1

                              Description

                              @zekageri

                              Goal

                              Make Phase safe and verifiable for the first public v0.1.0 release.

                              This plan is based on the release review of main at commit 00b6ac9a6b4660f83c2547795d84e6421ad0414e.

                              Release blockers

                              1. Make task teardown ownership race-free

                              The worker currently clears taskRunning and taskHandle before it has actually finished executing and deleted/suspended itself. Phase::end() may therefore return and allow _impl destruction while the worker still accesses PhaseImpl.

                              • Redesign worker termination so end() cannot observe completion before the task has stopped accessing PhaseImpl.
                              • Prefer an explicit worker-exit handshake:
                                • worker completes shutdown and final diagnostics
                                • worker signals exitReady
                                • worker suspends or waits without touching PhaseImpl
                                • caller-side end() deletes the worker task using the stored handle
                                • only then clear the handle and mark the object ended
                              • Support both normal FreeRTOS tasks and capability-created PSRAM-stack tasks.
                              • Ensure task memory is released exactly once.
                              • Ensure semaphore/mutex destruction only happens after worker termination.
                              • Add deterministic tests for end() from Idle, Booting, Starting, Ready, Paused, Stopping, Failed, and Stopped.
                              • Add a repeated create/init/start/end/destroy stress test.

                              2. Reject end() from the Phase task

                              Callbacks execute synchronously on the Phase task. Calling end() from a lifecycle callback, group condition, onChange, onReady, or onFailed can make the task wait for itself.

                              • Detect xTaskGetCurrentTaskHandle() == taskHandle in end().
                              • Return a clear non-success result without blocking.
                              • Document that end() must be called by another task.
                              • Define destructor behavior when destruction is attempted from the Phase task.
                              • Add tests covering end() from every callback type.
                              • Verify that stop(), pause(), and resume() remain safe when called from callbacks.

                              3. Fix callback string lifetime guarantees

                              PhaseChange::pauseReason points into mutable internal storage. Another task can call resume() while the callback is running and invalidate the pointer.

                              • Snapshot pauseReason into storage whose lifetime covers the complete callback invocation.
                              • Review nodeName and message for the same guarantee.
                              • Keep callbacks outside the internal mutex.
                              • Add a concurrency test where another task changes pause state while onChange is executing.
                              • Keep the documented guarantee that pointers are valid for the duration of the callback.

                              4. Remove lifecycle-time dynamic allocations

                              Runtime snapshot objects currently copy std::string, dependency vectors, and std::function instances during boot/readiness/shutdown.

                              • Make the registered graph immutable after registration closes.
                              • Replace allocating runtime snapshots with index-based or fixed lightweight snapshots.
                              • Avoid copying dependency vectors during lifecycle evaluation.
                              • Avoid copying node names during normal lifecycle execution where a stable immutable reference/index is sufficient.
                              • Avoid copying std::function unless the implementation proves the copy is non-allocating and safe.
                              • Ensure no lifecycle path can terminate because a snapshot allocation failed.
                              • Update documentation to accurately distinguish setup-time allocation from runtime behavior.
                              • Add an allocation audit/test around start, group polling, stop, rollback, and end.

                              5. Add behavioral test coverage

                              Current CI compiles examples across multiple ESP32 targets but does not validate state-machine behavior.

                              • Add deterministic host tests or an ESP32 test harness for:
                                • dependency-ordered init
                                • dependency-ordered start/readiness
                                • reverse stop order
                                • reverse deinit order
                                • required init failure rollback
                                • required start failure rollback
                                • optional init failure
                                • optional start failure and deinit cleanup
                                • optional dependent skipping
                                • missing dependency validation
                                • circular dependency validation
                                • group success
                                • group timeout
                                • pause/resume during group polling
                                • stop immediately after start()
                                • stop after the worker consumes the start request
                                • repeated stop requests
                                • restart after Stopped
                                • concurrent diagnostics/state reads
                                • callback-triggered stop/pause/resume
                                • callback-triggered end() rejection
                                • teardown synchronization
                              • Make behavioral tests blocking in CI.
                              • Keep multi-board example builds as compatibility checks.

                              6. Align release metadata with the tag

                              • Set library.json version to 0.1.0.
                              • Set library.properties version to 0.1.0.
                              • Update README status from 0.0.1 to 0.1.0.
                              • Add a blocking CI check that vX.Y.Z matches both manifests.
                              • Run Arduino metadata lint as a blocking release prerequisite, or add an equivalent strict metadata check.

                              Correctness and observability fixes

                              7. Correct stack high-water-mark units

                              ESP-IDF reports uxTaskGetStackHighWaterMark() in bytes. Phase currently multiplies it by sizeof(StackType_t).

                              • Return the value without multiplying it.
                              • Add a focused test or platform assertion for the diagnostic unit.
                              • Confirm documentation says bytes.

                              8. Define shutdown failure semantics

                              Stop/deinit callback failures are currently emitted but discarded while shutdown still ends in Stopped.

                              Choose and document one policy:

                              • Best effort: continue shutdown, aggregate failures, expose the final shutdown result and diagnostics.

                              or

                              • Event-only: continue shutdown and explicitly document that stop/deinit failures are observable only through onChange().

                              Also:

                              • Preserve cleanup progress even when one stop/deinit callback fails.
                              • Add tests with multiple failing shutdown callbacks.

                              API and documentation pass

                              • Document exact async semantics of start(), stop(), pause(), and resume().
                              • Document allowed state transitions and restart behavior after Stopped.
                              • Document that callback timeouts are post-return measurements and cannot interrupt stuck callbacks.
                              • Document callback execution context and reentrancy rules.
                              • Document end() as terminal and external-task-only.
                              • Document allocation behavior after runtime-allocation changes.
                              • Verify every example checks registration builder results where practical.
                              • Add a release changelog summarizing the supported API and known cooperative limitations.

                              Recommended implementation order

                              1. Teardown ownership and worker-exit handshake.
                              2. Self-end()/destructor policy.
                              3. Callback pointer lifetime fixes.
                              4. Runtime snapshot/allocation redesign.
                              5. Behavioral test harness and race tests.
                              6. Stack diagnostic and shutdown-result fixes.
                              7. Metadata, documentation, and release validation.

                              Release acceptance criteria

                              v0.1.0 is ready when:

                              • No task can access PhaseImpl after end() returns.
                              • end() cannot deadlock when invoked from the Phase task.
                              • All PhaseChange pointers remain valid throughout callback execution.
                              • Lifecycle execution does not depend on unhandled dynamic allocations.
                              • Behavioral tests cover success, failure, rollback, pause, cancellation, restart, and teardown races.
                              • CI is green for behavioral tests and all supported board builds.
                              • Release tag and manifest versions match 0.1.0.
                              • Documentation matches the implemented lifecycle and shutdown semantics.

                              Activity

                              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions