Skip to content

feat(scenario-up): source-pinned platform/KAS/Go-SDK in instance mode (DSPX-3356 repro)#460

Draft
dmihalcik-virtru wants to merge 16 commits into
mainfrom
reproducing-things
Draft

feat(scenario-up): source-pinned platform/KAS/Go-SDK in instance mode (DSPX-3356 repro)#460
dmihalcik-virtru wants to merge 16 commits into
mainfrom
reproducing-things

Conversation

@dmihalcik-virtru
Copy link
Copy Markdown
Member

@dmihalcik-virtru dmihalcik-virtru commented May 22, 2026

Summary

  • Adds full instance-mode support for source: pins in otdf-local and otdf-sdk-mgr — previously only dist: pins worked.
  • Adds install_go_from_platform to otdf-sdk-mgr so Go SDK built from the platform monorepo (source: platform) can be used in scenarios.
  • Fixes five separate _require_platform_dir() crashes in otdf-local when no sibling platform/ checkout exists.
  • Adds xtest/scenarios/dspx-3356.yaml to reproduce DSPX-3356.

Bug confirmed

test_pqc.py::test_xwing_roundtrip FAILS at platform main (commit 1209acc2):

AssertionError: X-Wing wrappedKey should be 1120 bytes, got 1190
assert 1190 == 1120

To reproduce

# Install artifacts (builds platform binary + Go SDK from platform@main)
uv run otdf-sdk-mgr install scenario xtest/scenarios/dspx-3356.yaml --skip-scripts

# Init + start instance
uv run otdf-local instance init dspx-3356 --from-scenario xtest/scenarios/dspx-3356.yaml
cd xtest/platform/src/main && bash .github/scripts/init-temp-keys.sh && cd -
uv run otdf-local --instance dspx-3356 up

# Set env
eval $(uv run otdf-local --instance dspx-3356 env)

# Run (bug reproduces)
uv run otdf-local scenario run xtest/scenarios/dspx-3356.yaml

Infrastructure changes

Component Change
otdf-sdk-mgr/installers.py install_go_from_platform(ref, dist_dir) builds otdfctl from platform monorepo worktree
otdf-sdk-mgr/cli_scenario.py Dispatch to platform build when sdks.*.source == "platform"
otdf-local/settings.py platform_source_dir property; docker_compose_file resolves from src worktree
otdf-local/platform.py _instance_dist_paths handles source pins; fallback to opentdf-dev.yaml
otdf-local/kas.py _instance_paths handles source pins
otdf-local/provisioner.py Use platform_source_dir as cwd
otdf-local/cli.py PLATFORM_VERSION from built binary; PLATFORM_DIR NoneType fix
otdf-local/cli_scenario.py Fix suite.selectsuite.targets; strip xtest/ prefix; set OTDFCTL_HEADS

Test plan

  • uv run otdf-sdk-mgr install scenario xtest/scenarios/dspx-3356.yaml --skip-scripts completes (builds platform + Go SDK)
  • uv run otdf-local --instance dspx-3356 up reaches "Environment is up!"
  • uv run otdf-local scenario run xtest/scenarios/dspx-3356.yaml exits 1 with AssertionError: X-Wing wrappedKey should be 1120 bytes, got 1190
  • A fix to platform that corrects the wrappedKey size causes the test to PASS

🤖 Generated with Claude Code

dmihalcik-virtru and others added 16 commits May 21, 2026 21:45
`install scenario` could not run as written: it iterated `ScenarioSdks.union()`
as a dict (it returns a list) and passed a `source=` kwarg `install_release`
does not accept. The emitted `installed.json` shape also did not match what
`scenario_to_pytest_sdks` reads (per-role lists, not sdk-name-keyed dict),
so even the platform-only path produced a manifest no downstream tool
could consume.

Source fixes:
- cli_scenario.py: iterate `union()` as the list it is, cache installs by
  (sdk, version, source), emit role-keyed lists matching the reader's
  expected shape; on failure write a partial manifest with `status=partial`
  so half-installed dist trees are diagnosable. Catch YAMLError in
  `_peek_kind` to surface a clean typer error.
- platform_installer.py: `_git_rev_parse` raises on failure instead of
  silently writing an empty `sha=` into `.version`. Missing `scripts/`
  raises instead of warning-and-continuing. SHA passthrough heuristic
  tightened from `>=7` chars to exactly 40 (SHA-1) or 64 (SHA-256), so
  ambiguous short tags like `abc1234` no longer skip the `service/`
  prefix. Dropped a docstring fragment pointing to a planning doc that
  won't exist post-merge.
- cli_install.py: dropped a docstring whose "deferred import" claim was
  false (the registration runs at module import). `lts platform` with no
  pinned version now exits 1 instead of warning-and-exit-0.

Tests:
- test_platform_installer.py: parametrized cases for `_resolve_platform_ref`
  covering version normalization, branch passthrough, the tightened hex
  heuristic, and SHA-1/SHA-256 passthrough.
- test_cli_scenario.py: end-to-end smoke that mocks the installers and
  asserts the produced manifest is round-trip consumable by
  `scenario_to_pytest_sdks`. This is the gating test that would have
  caught the original bug.

79 passing (was 67).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- platform_installer: fix worktree update from bare repo (no `origin`
  remote exists), use `git reset --hard <branch>` instead of `git pull`
- platform_installer: stop swallowing subprocess output so long-running
  `go build`/`git clone` progress is visible to the user
- cli_install: extract `_install_platform_or_exit` to dedupe platform
  handling across `lts`, `tip`, and `release`
- cli_scenario: parse manifest YAML once and dispatch by `kind`, instead
  of peeking + re-parsing in each loader

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…typing

- AGENTS.md: add "Before Committing Python Changes" section requiring
  `uv run ruff check`, `uv run ruff format`, and `uv run pyright` on any
  touched Python package before commit. Explicitly call out that `uvx`
  must NOT be used for pyright (isolated env can't see project deps, so
  every project import becomes a spurious "could not be resolved" error).
- cli_scenario: split the single `dict[str, object]` install record into
  per-section typed containers (`installed_platform`, `installed_kas`,
  `installed_sdks`) assembled at write time via a `_snapshot()` helper.
  Fixes pre-existing pyright `__setitem__ ... not defined on object`
  errors at the nested writes; on-disk JSON shape is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Root AGENTS.md: add a Repository Layout table near the top, correct
  the `platform/` description (it's installed by `otdf-sdk-mgr install`,
  not committed source), and trim the duplicated "Summary → Preferred
  Workflow" block that restated the body.
- otdf-local/AGENTS.md: lead with the dependency on `otdf-sdk-mgr`
  (otdf-local launches the binaries the installer produces). Mark the
  manual-keys YAML block as an emergency fallback that may drift.
- otdf-sdk-mgr/AGENTS.md (new): operational guide for the installer —
  subcommand layout, bare-clone-worktree gotchas (no `origin` remote,
  namespaced `service/vX.Y.Z` tags, unbuffered subprocess output),
  pattern for adding a new subcommand.
- xtest/AGENTS.md (new): test-suite layout, custom pytest options,
  audit-log fixture quick reference, authoring guidance.
- otdf-sdk-mgr/CLAUDE.md, xtest/CLAUDE.md: symlinks to AGENTS.md to
  match the repo convention.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Refactors otdf-local from a single-instance CLI (one platform checkout,
fixed ports, hardcoded six KAS instances) into a multi-instance harness
where each named instance under tests/instances/<name>/ owns its own
opentdf.yaml, keys, KAS configs, and port range.

Why
---

A single bug report often describes a *combination* — platform v0.9.0
with Java SDK 0.7.8 and a KAS at a pre-release. Today a developer has
to hand-edit configs and re-checkout the platform to reproduce. After
this change:

  otdf-local instance init java-078 --from-scenario .../scenario.yaml
  otdf-local --instance java-078 up

brings up exactly the topology the scenario describes, using platform
binaries that otdf-sdk-mgr already provisioned (each instance, and each
KAS within an instance, can reference a different pinned version). Two
instances on disjoint ports.base can coexist on a developer laptop.

What changes
------------

otdf-local now depends on otdf-sdk-mgr via a uv path source so both
tools share the canonical Scenario/Instance schema.

Settings (otdf_local.config.settings):
  - New instance_name (env-overridable via OTDF_LOCAL_INSTANCE_NAME),
    instance_dir, instances_root, instance_yaml properties.
  - platform_dir becomes optional; legacy sibling-discovery only kicks
    in when no per-instance configuration is present.
  - platform_binary_for(dist) resolves to the otdf-sdk-mgr-managed
    xtest/platform/dist/<dist>/service binary.
  - keys_dir, logs_dir, config_dir, platform_config, and
    get_kas_config_path switch to per-instance paths whenever
    instance.yaml exists; legacy behavior is preserved otherwise.
  - load_instance() reads the per-instance manifest via the shared
    Pydantic model.

Ports (otdf_local.config.ports):
  - KAS_OFFSETS exposes the offset table (alpha=+101, beta=+202, ...,
    km2=+606) so multiple instances on different bases get disjoint
    port ranges. The legacy 8080-based constants are preserved as
    defaults.
  - get_kas_port(name, base=...) computes the port relative to base.

Services (otdf_local.services.platform / .kas):
  - PlatformService.start() and KASService.start() use the pinned dist
    binary at xtest/platform/dist/<dist>/service when an instance is
    loaded, with cwd set to the recorded worktree so the binary finds
    its embedded resources. Legacy `go run ./service` path runs
    unchanged when no instance is active.
  - KASService.is_key_management defers to the manifest's `mode` field
    instead of the legacy name-based heuristic; per-KAS features (e.g.
    ec_tdf_enabled) pass through to opentdf.yaml.
  - KASManager constructs only the KAS instances listed in
    instance.yaml's kas: map. start_standard / start_km filter on
    is_key_management so subset topologies still work.

utils.keys.setup_golden_keys:
  - Writes key files into the target directory (per-instance keys_dir
    or legacy platform_dir) and uses absolute paths in the generated
    keys_config so the binary finds them regardless of cwd.

CLI:
  - New top-level --instance option threads through every command via
    OTDF_LOCAL_INSTANCE_NAME.
  - New `instance` subcommand group: init [--from-scenario PATH],
    ls --json, rm.
  - New `scenario` subcommand: `run <path>` translates the scenario's
    suite block into `pytest --sdks-encrypt ... --sdks-decrypt ...
    --containers ...` under xtest/ with OTDF_LOCAL_INSTANCE_NAME set.

Tests (otdf-local/tests/test_multi_instance.py):
  - Port arithmetic at default and alternate bases.
  - Settings round-trip with and without an instance.yaml.
  - platform_binary_for resolves under the otdf-sdk-mgr-managed
    xtest/platform/ tree.

.gitignore additions:
  - tests/instances/ (per-instance config and logs)
  - xtest/scenarios/*.installed.json (provisioning records)
  - .claude/tmp/

Backward compatibility:
  - `otdf-local up` with no --instance flag keeps working against a
    sibling platform/ checkout.

Refs: https://virtru.atlassian.net/browse/DSPX-3302

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two new pytest CLI options so xtest can be driven by a scenarios.yaml
and run against a specific otdf-local instance.

  --scenario PATH    When set, defaults --sdks-encrypt, --sdks-decrypt,
                     and --containers from the scenario's `sdks` and
                     `suite` blocks. Options explicitly passed on the
                     CLI always override.
  --instance NAME    Propagated to OTDF_LOCAL_INSTANCE_NAME so child
                     `otdf-local` invocations within the test see the
                     same instance the scenario expects.

If otdf-sdk-mgr is not installed (minimal pytest environments), the
--scenario flag silently no-ops via an ImportError guard. The flag
shape is invariant either way so CI configs don't fork.

This is the consumer side of the PR 3 / scenario-driven flow: the
authoritative entry point remains `otdf-local scenario run <path>`,
which sets these flags for you; this PR lets pytest accept them
directly when running scenario-aware sessions outside the wrapper.

Refs: https://virtru.atlassian.net/browse/DSPX-3302

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds five Claude Code skills under tests/.claude/skills/ that together
turn a Jira bug ticket into a running reproduction, plus a downstream-
installable plugin manifest under .claude/plugin/.

Why
---

The end-to-end goal of DSPX-3302 is to make bug reproduction approachable
for QA, downstream-product engineers, and CI. PRs 1-4 build the plumbing
(shared schema, platform installer, multi-instance otdf-local, xtest
conftest hooks). This PR is the user-facing surface: a Claude can pull
context from Jira, draft an xtest/scenarios/<jira-key>.yaml (and, when
needed, an xtest/bug_<jira_key>_test.py), bring the environment up at
the right version pins, run the scenario's pytest selection, and tear
down.

Skills
------

  scenario-from-bug-report
    Pulls the Jira issue and its comments via `acli jira workitem view
    --fields '*all' --json` and `acli jira workitem comment list`,
    extracts version pins / KAS topology / container type / feature
    flags, then writes xtest/scenarios/<jira-key-lowercased>.yaml
    validated against otdf_sdk_mgr.schema.Scenario. Drafts a new
    xtest/bug_<id>_test.py only when no existing pytest covers the
    case; never silently lands assertions.

  scenario-up
    Runs `otdf-sdk-mgr install scenario`, then `otdf-local instance
    init --from-scenario`, then `otdf-local --instance <name> up`, and
    polls status until healthy. Surfaces logs rather than retrying
    blindly when something stays unhealthy.

  scenario-run
    Invokes `otdf-local scenario run <path>` and classifies the
    result: "bug reproduced" / "not reproduced" / "unrelated failure".
    Cites the evidence line and points at per-service logs.

  scenario-tear-down
    Stops the instance and optionally removes the directory after
    explicit user confirmation.

  instance-status
    Lists known instances, their port bases, health, and flags port
    collisions.

Jira-safety
-----------

Permissions in both .claude/settings.json and the plugin manifest
allow only read+comment via acli jira: workitem view, workitem search,
workitem comment list, workitem comment create, plus a handful of
read-only project/board/sprint queries. edit, delete, transition,
assign, archive, link create, watcher add are all denied. The
plugin.json carries a permission_notes block explaining the policy.

Plugin manifest
---------------

.claude/plugin/plugin.json declares the skill names, runtime
requirements (uv, go, git, docker, acli), and the canonical permission
allowlist, so downstream first/third-party integrators can install
this plugin into their own Claude Code setups.

Refs: https://virtru.atlassian.net/browse/DSPX-3302

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m-ticket (DSPX-3302)

Headless dogfooding (run-1 on DSPX-2719) showed the bug-only framing
was too narrow — the common workflow is writing tests for new features
first (TDD), not reproducing version-pinned bugs.

- Rename and rewrite the skill to branch on Jira Issue Type. Bug
  follows the old expected/actual flow; Story/Task uses ref pins
  (`main`, feature branch, PR SHA via `gh pr view --json headRefOid`)
  for forward-looking regression gates; Spike bails out rather than
  fabricating. Mandates `acli workitem comment list` and steers away
  from cli.sh greps (both were run-1 gaps).
- New `scenario-matrix` sibling skill: write N scenario files from a
  base × N refs (PRs/branches/releases). Schema/installer support was
  already there via `PlatformPin.source.ref` and
  `install_platform_source(ref)` — no other changes needed.
- `scenario-run` output classification generalized from "bug
  reproduced / not reproduced" to "expected / unexpected outcome",
  with explicit branches for bug-repro vs TDD interpretations.
- `scenario-up` description and `plugin.json` (description, skills
  array, requirements) updated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
For features (or bugs) that touch more than one OpenTDF repo — platform
plus the Go / Java / JS SDKs — feature-design captures the work as a
single spec at xtest/features/<name>.yaml plus the tests-side artifacts
that land first (feature_type entry in tdfs.py, scenario, draft test).

The model matches the team's existing pattern: tests-side artifacts
merge first, dormant under a `supports("<feature>")` gate, and each
per-repo PR activates the gate by adding `supports <feature>` to its
cli.sh. PRs land async, in any order; no cross-PR lockstep needed.

- `feature-design` SKILL: propose-then-iterate authoring from a Jira
  ticket (or free-form description). Drafts a complete spec on the
  first pass, asks one composite redirect question, then writes the
  spec + patches tdfs.py + invokes scenario-from-ticket internally
  to produce the dormant scenario and draft test. Bails on Spike or
  unclear tickets rather than fabricating.
- `xtest/features/{README,CLAUDE}.md`: progressive-disclosure docs —
  human-facing README and agent-facing CLAUDE.md.
- `xtest/README.md` gains a brief "Test artifact directories" section
  pointing at scenarios/ and features/.
- `settings.json` + `plugin.json`: Write(xtest/features/**) allowlist,
  feature-design added to plugin skills array.

The complementary feature-orchestrate skill (fanning out per-repo
subagents to draft impl PRs in each touched repo) is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…PX-3302)

Headless dogfooding (runs 1 and 2 of scenario-from-ticket on DSPX-2719)
surfaced two real gaps:

- The `Skill` tool was denied on both runs because the allowlist didn't
  cover it, so the body of SKILL.md wasn't injected on invocation; the
  agent had to manually `Read` the skill file ~25 turns in, wasting time
  and biasing exploration toward grepping unrelated files first.
  Add `Skill(*)` to settings.json and per-skill `Skill(<name>)` entries
  to plugin.json (the latter enumerates exactly what downstream installs
  get, since they shouldn't inherit a wildcard).
- `acli jira workitem comment list` requires `--key <KEY>` (the
  subcommand differs from `view`, which takes the key positionally).
  Both scenario-from-ticket and feature-design had the wrong form;
  corrected, with a one-line note about the asymmetry so the next
  agent doesn't paraphrase.

Verified via run-3 on DSPX-2719: 41 turns / 5m16s / $1.07 (vs run-1's
48 turns / 6m44s / $1.27). Skill tool returned success on first call,
both acli commands ran cleanly, the Story/Task branch produced
`source.ref: main` pins correctly (no more incorrectly defaulting to
`dist: lts`), and the agent's `actual:` field correctly enumerated all
three test-infrastructure prerequisites including a `with_ecdsa_binding`
parameter that run-1's scenario missed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emas (DSPX-3302)

Headless runs of scenario-from-ticket kept trying `python3 -c "from
otdf_sdk_mgr.schema import Scenario; ..."` to introspect Pydantic model
shape while authoring scenarios. That form isn't in the plugin's Bash
allowlist (deliberately — it's arbitrary code execution), so the agent
fell back to Reading schema.py source. Static, committed JSON Schemas
give the same information declaratively without needing a python verb
in the allowlist at all.

- `otdf-sdk-mgr schema dump [--out-dir]`: writes
  `xtest/schema/{scenario,instance}.schema.json` from
  `Model.model_json_schema()`, sorted-keys + trailing newline so output
  is byte-stable. Add new models to `SCHEMAS` in cli_schema.py and they
  get picked up automatically.
- `xtest/schema/` is committed with the generated files plus brief
  README/CLAUDE.md (progressive-disclosure, mirroring xtest/features/).
- `test_schema_sync.py` parametrizes over `SCHEMAS` and fails if any
  committed file drifts from the live model — the safety net for
  "someone edited a Pydantic model without regenerating."
- `scenario-from-ticket` SKILL.md Step 5 now points at
  `xtest/schema/scenario.schema.json` as the canonical field list.
- `xtest/README.md` lists the new directory alongside `scenarios/` and
  `features/`.

No allowlist changes needed — `Bash(uv run otdf-sdk-mgr *)` already
covers the dump subcommand, and `Read` is unrestricted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nce mode (DSPX-3356)

Previously `otdf-local --instance <id> up` and `otdf-sdk-mgr install scenario`
only handled `dist:` pins. Source pins (`source: { ref: main }`) failed with
"No sibling platform/ directory found" and Go SDK at `version: main` failed with
a module-proxy 404.

Changes:
- **otdf-sdk-mgr**: add `install_go_from_platform(ref, dist_dir)` — builds
  otdfctl from the platform monorepo worktree (needed when `version: main` can't
  be resolved via the Go module proxy). `cli_scenario.py` dispatches to it when
  `sdks.*.source == "platform"`.
- **otdf-local/settings**: add `platform_source_dir` property that returns the
  platform src worktree in instance+source mode. `docker_compose_file` uses it
  to find docker-compose.yaml in the worktree.
- **otdf-local/platform**: `_instance_dist_paths` handles `source` pins (not
  just `dist`). `_generate_config` falls back to `opentdf-dev.yaml` when the
  worktree has no `opentdf.yaml`.
- **otdf-local/kas**: `_instance_paths` handles `source` pins.
- **otdf-local/provisioner**: use `platform_source_dir` as cwd.
- **otdf-local/cli** (env): emit `PLATFORM_VERSION` by reading it from the
  built binary directly (fast path), with `go run ./service version` fallback.
  Fix `service version` printing to stderr. Fix `PLATFORM_DIR` NoneType crash.
- **otdf-local/cli_scenario** (run): fix `suite.select` → `suite.targets`;
  strip leading `xtest/` from target paths; set `OTDFCTL_HEADS` from the
  installed.json Go dist name so `load_otdfctl()` finds the right binary.
- **xtest/scenarios**: add `dspx-3356.yaml` — reproduces X-Wing wrappedKey
  size bug (1190 bytes vs expected 1120) at platform@main.

Bug confirmed: `test_xwing_roundtrip` FAILS with
`AssertionError: X-Wing wrappedKey should be 1120 bytes, got 1190`.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 020e78b2-59bc-4449-9c86-664c96683e6e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch reproducing-things

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a multi-instance refactor for the OpenTDF test harness, enabling concurrent and isolated test environments. Key updates include new instance management commands in otdf-local, platform service installation via git worktrees in otdf-sdk-mgr, and scenario-driven test execution integrated with pytest. Feedback highlights a critical merge conflict marker left in xtest/AGENTS.md that needs to be resolved.

Comment thread xtest/AGENTS.md
| `fixtures/` | Module-scoped pytest fixtures: `attributes.py`, `keys.py`, `audit.py`, `assertions.py`, `kas.py`, `encryption.py`, `obligations.py`. |
| `tdfs.py` | SDK abstraction layer — wraps the `cli.sh` shims under `sdk/<lang>/dist/<version>/`. |
| `sdk/{go,java,js}/dist/<version>/` | SDK CLI builds. Installed by `otdf-sdk-mgr install` (see `../otdf-sdk-mgr/README.md`). |
<<<<<<< HEAD
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

A merge conflict marker (<<<<<<< HEAD) has been accidentally left in the file. This should be removed to maintain the integrity of the documentation.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant