Purpose
-------
Parse + validate the spec-10 §9 workspace-manifest ``test`` blocks
(Plan 189). The shipping v0.25 base loader
(``oct.core.workspace.load_workspace``) owns the base schema and
deliberately ignores unknown keys; this module layers the §9 test
configuration on top by re-reading the raw manifest for the additive
keys (``subprojects[].test``, top-level ``default_test_profile``)
without touching the base loader.
Responsibilities
----------------
- :func:`load_test_manifest` - load the base workspace via the v0.25
loader, then parse/validate the test blocks into a
:class:`TestManifest` of :class:`PackageTestConfig` entries.
- Own the Plan-239 P1 ``max_workers`` key: the per-package cap on test
workers, validated as a positive int, defaulted to
:data:`DEFAULT_MAX_WORKERS`. Both the pytest side
(``oct.testing.parallel``) and the §20 stand-in read this one key.
- Map every invalid-manifest condition to the §12.4 taxonomy as data
(:class:`oct.testing.errors.OctTestError`): a base-loader rejection
is the documented ``9101``; a test-config content violation is the
documented ``9102``; a missing manifest is
``WORKSPACE_FILE_NOT_FOUND``. NO ``sys.exit`` here - exits are
mapped by the future CLI slice.
- Pin the TEST profile vocabulary (``strict``/``smoke``/``full``) -
distinct from the LINT profiles (``proto``/``compact``/``strict``)
that live in ``subprojects[].profile`` (the §9 vocabulary hazard).
Diagnostics
-----------
Domain: OCT-TESTING
Levels:
L2: lifecycle (load start / end)
L3: semantic details (per-package parse results)
L4: deep tracing
Contracts
---------
- ``oct.core.workspace`` is a READ-ONLY dependency: base-schema
semantics are exactly the v0.25 loader's, witnessed by
``tests/tests_testing/test_loader_tolerance_witness.py``.
- ``test_roots`` are SUBPROJECT-ROOT-RELATIVE (§9): absolute entries
and entries escaping the subproject root are configuration errors.
- An ENABLED test block must declare non-empty ``test_roots`` -
in workspace mode the manifest roots are authoritative (ADR-T-18),
so an enabled package without roots is a misdeclaration.
- Existence of declared roots is NOT checked here: stale/empty roots
are collection-time findings (§10 / §12.4 ``EMPTY_TEST_ROOT`` /
``UNDECLARED_TEST_ROOT``), owned by the future collection slice.
- ``max_workers`` refuses rather than degrades: a non-positive or
non-integer value raises ``9102``, because a silently-defaulted cap
is exactly the kind of configuration drift a run cannot detect.
Dependencies
------------
- oct.core.workspace (read-only: base schema + resolved paths)
- oct.core.diagnostics (_dbg)
- oct.testing.errors (the §12.4 taxonomy as data)