Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .apm/architecture/owners/contracts-tooling.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
{
"version": 1,
"owners": [
{
"id": "contract-package-source",
"decision": "Explicit package contract acquisition without project installation or a second execution engine",
"owner": "install/contract_source.py via canonical dependency owners; contracts/frontend.py admits the job",
"selectors": ["src/apm_cli/apmx.py", "src/apm_cli/install/contract_source.py"],
"guards": ["contracts-package-source"]
},
{
"id": "contract-leaf-source",
"decision": "Strict leaf contract source selection and planning without legacy fallback",
"owner": "contracts/frontend.py with canonical yaml_io and dependency owners",
"selectors": ["src/apm_cli/contracts/frontend.py", "src/apm_cli/contracts/imports.py"],
"guards": ["contracts-leaf-source"]
},
{
"id": "contract-exact-subject",
"decision": "Raw-byte input, baseline and artifact identity for local contracts",
"owner": "contracts/workspace.py",
"selectors": ["src/apm_cli/contracts/workspace.py"],
"guards": ["contracts-leaf-subject"]
},
{
"id": "contract-process-observation",
"decision": "Managed contract process creation, cancellation and termination observations",
"owner": "contracts/process.py",
"selectors": ["src/apm_cli/contracts/process.py"],
"guards": ["contracts-leaf-process"]
},
{
"id": "contract-leaf-outcome",
"decision": "Contract check normalization, terminal reduction and atomic run records",
"owner": "contracts/records.py with outcome vocabulary in contracts/models.py",
"selectors": ["src/apm_cli/contracts/records.py", "src/apm_cli/contracts/models.py"],
"guards": ["contracts-leaf-outcome"]
},
{
"id": "onboarding-metadata-only",
"decision": "Discovery inventory and consented consumer dependency delta; never source or deployment mutation",
Expand Down
36 changes: 36 additions & 0 deletions .apm/docs-index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,42 @@ pages:
flags: [--target, -g]
schemas: [marketplace.json, settings.local.json, apm-registration.json]

- path: docs/src/content/docs/consumer/run-contracts.md
title: "Run a contract"
persona: consumer
promise: 1
documents_symbols:
cli: [apmx]
flags: [--from, --plan, --on, --model, --allow-host-access]
schemas: [.contract.md]

- path: docs/src/content/docs/reference/cli/apmx.md
title: "apmx"
persona: consumer
promise: 1
documents_symbols:
cli: [apmx]
flags: [--from, --plan, --on, --model, --allow-host-access, --verbose, --version]
schemas: [.contract.md, apm.yml.dependencies, apm.lock.yaml]

- path: docs/src/content/docs/reference/cli/plan.md
title: "apm plan"
persona: consumer
promise: 1
documents_symbols:
cli: [apm plan, apmx]
flags: [--on, --model, --verbose]
schemas: [.contract.md, apm.yml.dependencies, apm.lock.yaml]

- path: docs/src/content/docs/reference/cli/run.md
title: "apm run"
persona: consumer
promise: 1
documents_symbols:
cli: [apm run]
flags: [--param, --verbose, --on, --model, --allow-host-access]
schemas: [apm.yml.scripts, .contract.md]

- path: docs/src/content/docs/producer/index.md
title: "Producer overview"
persona: producer
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ jobs:
run: |
uv run pwsh scripts/windows/sign-binary.ps1

- name: Smoke companion executable
shell: bash
env:
BINARY_DIR: dist/${{ matrix.binary_name }}
run: |
companion="$BINARY_DIR/apmx"
if [ -f "$companion.exe" ]; then companion="$companion.exe"; fi
"$companion" --version
"$companion" --help

- name: Test current Windows companion installation
if: matrix.platform == 'windows'
shell: pwsh
run: |
uv run pwsh -NoProfile scripts/windows/test-install-script.ps1 -LocalBundle dist/apm-windows-x86_64
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Upload binary as workflow artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ WIP/
skill-plan.md
skill-strategy.md
apm_modules/
.apm/runs/
build/tmp/
scout-pipeline-result.png
.copilot/
Expand Down
4 changes: 3 additions & 1 deletion CONFORMANCE.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,8 +1046,10 @@
"keyword": "MUST",
"section": "7.5",
"status": "active",
"test_count": 1,
"test_count": 3,
"tests": [
"tests/spec_conformance/test_resolution_reqs.py::test_packaged_contract_replays_caller_lock_without_resolution[False]",
"tests/spec_conformance/test_resolution_reqs.py::test_packaged_contract_replays_caller_lock_without_resolution[True]",
"tests/spec_conformance/test_resolution_reqs.py::test_resolver_replays_locked_commit_without_network"
]
},
Expand Down
2 changes: 1 addition & 1 deletion CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Repository-coordinate segments are case-insensitive for `github.com`, GitHub Ent
| [req-rs-012](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-012) | MUST | 7.7 | consumer | active | 1 | - |
| [req-rs-013](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-013) | MUST | 7.2 | consumer | active | 1 | - |
| [req-rs-014](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-014) | MUST | 7.3.1 | consumer | active | 1 | - |
| [req-rs-015](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-015) | MUST | 7.5 | consumer | active | 1 | - |
| [req-rs-015](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-015) | MUST | 7.5 | consumer | active | 3 | - |
| [req-rs-016](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-016) | MUST | 7.2 | consumer | active | 10 | - |
| [req-rs-017](docs/src/content/docs/specs/openapm-v0.1.md#req-rs-017) | MUST | 7.7 | consumer | active | 15 | - |
| [req-sc-001](docs/src/content/docs/specs/openapm-v0.1.md#req-sc-001) | MUST | 10.4 | consumer | active | 2 | - |
Expand Down
40 changes: 31 additions & 9 deletions build/apm.spec
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ def _read_version_from_pyproject(repo_root):
return (0, 0, 0, 0)
return (int(parts.group(1)), int(parts.group(2)), int(parts.group(3)), 0)

_win_version_info = None
if sys.platform == 'win32':
def windows_version_info(executable_name):
"""Describe each Windows entry point without anonymous PE metadata."""
if sys.platform != 'win32':
return None
try:
from PyInstaller.utils.win32 import versioninfo as vi
_ver = _read_version_from_pyproject(repo_root)
_ver_str = f'{_ver[0]}.{_ver[1]}.{_ver[2]}'
_win_version_info = vi.VSVersionInfo(
return vi.VSVersionInfo(
ffi=vi.FixedFileInfo(
filevers=_ver,
prodvers=_ver,
Expand All @@ -70,18 +72,18 @@ if sys.platform == 'win32':
vi.StringStruct('FileDescription',
'APM - Agent Package Manager'),
vi.StringStruct('FileVersion', _ver_str),
vi.StringStruct('InternalName', 'apm'),
vi.StringStruct('InternalName', executable_name),
vi.StringStruct('LegalCopyright',
'Copyright (c) Microsoft Corporation'),
vi.StringStruct('OriginalFilename', 'apm.exe'),
vi.StringStruct('OriginalFilename', executable_name + '.exe'),
vi.StringStruct('ProductName', 'APM'),
vi.StringStruct('ProductVersion', _ver_str),
])]),
vi.VarFileInfo([vi.VarStruct('Translation', [1033, 1200])]), # LCID 1033 = en-US, Codepage 1200 = UTF-16
],
)
except ImportError:
_win_version_info = None
return None

# APM CLI entry point
entry_point = repo_root / 'src' / 'apm_cli' / 'cli.py'
Expand Down Expand Up @@ -270,7 +272,7 @@ excludes = [
]

a = Analysis(
[str(entry_point)],
[str(entry_point), str(repo_root / 'src' / 'apm_cli' / 'apmx.py')],
pathex=[str(repo_root / 'src')],
binaries=[],
datas=datas,
Expand Down Expand Up @@ -331,13 +333,18 @@ if sys.platform == 'linux':

pyz = PYZ(a.pure, a.zipped_data, cipher=None)

# Both entry scripts share the analysis, PYZ and onedir support files. Keep
# runtime hooks in each EXE, but never execute the other CLI's entry script.
apm_scripts = [script for script in a.scripts if script[0] != 'apmx']
apmx_scripts = [script for script in a.scripts if script[0] != 'cli']

# GNU strip corrupts Windows PE/COFF binaries; only enable on Unix
_strip = sys.platform != 'win32'

# Switch to --onedir for directory-based deployment (faster startup with --onedir)
exe = EXE(
pyz,
a.scripts,
apm_scripts,
[], # Empty for --onedir mode
exclude_binaries=True, # Exclude binaries for --onedir mode
name='apm',
Expand All @@ -353,11 +360,26 @@ exe = EXE(
target_arch=None,
codesign_identity=None,
entitlements_file=None,
version=_win_version_info,
version=windows_version_info('apm'),
)

apmx_exe = EXE(
pyz,
apmx_scripts,
[],
exclude_binaries=True,
name='apmx',
debug=False,
bootloader_ignore_signals=False,
strip=_strip,
upx=should_use_upx(),
console=True,
version=windows_version_info('apmx'),
)

coll = COLLECT(
exe,
apmx_exe,
a.binaries,
a.zipfiles,
a.datas,
Expand Down
3 changes: 2 additions & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default defineConfig({
},
{
label: 'CLI reference',
description: 'Per-command reference for the apm CLI.',
description: 'Per-command reference for apm and apmx.',
paths: ['reference/cli/**'],
},
],
Expand All @@ -171,6 +171,7 @@ export default defineConfig({
{ label: 'Install packages', slug: 'consumer/install-packages' },
{ label: 'Manage dependencies', slug: 'consumer/manage-dependencies' },
{ label: 'Run scripts', slug: 'consumer/run-scripts' },
{ label: 'Run a contract', slug: 'consumer/run-contracts' },
{ label: 'Update and refresh', slug: 'consumer/update-and-refresh' },
{ label: 'Install MCP servers', slug: 'consumer/install-mcp-servers' },
{ label: 'Install LSP servers', slug: 'consumer/install-lsp-servers' },
Expand Down
64 changes: 64 additions & 0 deletions docs/src/content/docs/consumer/run-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Run a contract
description: Run one packaged job with caller-owned inputs, independent checks, and retained output.
sidebar:
order: 4
---

Use `apmx`, bundled with APM, to run one contract and assess its output with
named checks. Existing [scripts](../run-scripts/) still use `apm run`.

## Prerequisites

- macOS/Linux, APM with `apmx`, authenticated native Copilot, Git, and Python 3.
- An independent, secret-free caller directory with no Git remote or configured
policy requirement.

Enable the experimental contract surface once:

```bash
apm experimental enable contracts
```

**Native execution is not a sandbox.** Review
[host access and policy limits](../../reference/cli/apmx/#native-execution-boundary).
Never remove remotes or policy to bypass a refusal.

## Run the packaged example

From the APM source checkout root, create a new, persistent caller directory
outside the checkout, then copy the supplied input. The caller must be
independent, with no remote or policy requirement:

```bash
package="$(pwd)/examples/contracts/packaged-job"
caller="$HOME/apmx-contract-example"
mkdir "$caller" &&
cp "$package/caller/notes.md" "$caller/notes.md" &&
cd "$caller" &&
apmx --from "$package" contracts/handoff.contract.md \
--on copilot --model gpt-6-astra --allow-host-access
```

`mkdir` refuses an existing directory; choose another unused path rather than
reusing it. Choose an accessible model. No caller manifest or install is required.
The package supplies its checker and one self-contained skill; `notes.md` comes
from the caller. Manifests, locks, and global configuration stay unchanged.

`--allow-host-access` lets Copilot and checks use host files, network and
available login details for this run. Run only contracts you trust.
An interactive spinner stays active during quiet work; Copilot's public
messages, tool activity and errors appear live above it.

## Inspect the result

Follow the reported paths to `.apm/runs/<run-id>/artifacts/` and `record.json`.
There is no automatic copy-back. The checker assesses JSON shape, exact source-ID
coverage, nonempty strings, and the skill's caution prefix, not factual accuracy.
Read the [outcomes](../../reference/cli/apmx/#results-and-retained-files) before
using the artifact.

For offline inspection, replace `--allow-host-access` with `--plan`. A missing skill
or unresolved remote source refuses without fetching. For local-file execution,
use `apmx ./handoff.contract.md --on copilot --allow-host-access`; see the
[source format](../../reference/cli/plan/#contract-source).
Loading