Command that triggered the bug
apiops publish
Expected behavior
normalizeSubscriptionScope in src/services/resource-publisher.ts documents the intent clearly:
Strip the service base ARM path prefix when it is present so that the extract → publish round-trip works without manual edits.
A subscription extracted from one instance and published to another should not need manual edits. properties.ownerId is a service-scoped ARM id in exactly the same way properties.scope is, so I would expect it to be normalized the same way.
Microsoft documents ownerId as relative:
ownerId — The user resource identifier of the subscription owner. The value is a valid relative URL in the format of /users/{userId} where {userId} is a user identifier.
SubscriptionContract.ownerId
Actual behavior
scope is normalized on publish, ownerId is not, so a subscription published to a different instance than it was extracted from carries the source instance's ownerId.
Extraction stores the ARM response as-is, which is deliberate:
// src/services/resource-extractor.ts
const safeJson = redactSecrets(descriptor, json);
// Write to artifact store (preserves opaque JSON per FR-009)
await store.writeResource(outputDir, descriptor, safeJson);
So the artifact on disk holds both ids absolute:
{
"properties": {
"scope": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{src-svc}/products/starter",
"ownerId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{src-svc}/users/1"
}
}
On publish, resource-publisher.ts:426 calls normalizeSubscriptionScope, which rewrites scope to /products/starter. Nothing does the equivalent for ownerId, so it is sent unchanged, still naming {src-svc}.
ownerId does not appear anywhere in src/. The only occurrence in the repository is a test fixture, tests/unit/services/resource-publisher.test.ts:1309, which sets it absolute alongside an absolute scope.
Suggested fix
Extend normalizeSubscriptionScope to cover ownerId, or add a sibling that applies the same service-prefix strip to it. The existing serviceScopeMatch regex already isolates the trailing APIM-relative segment, so the transform is the same shape: .../service/{svc}/users/1 becomes /users/1.
Worth noting ownerId differs from scope in one respect: envMapping affixes should almost certainly not apply to it, since a user id is not an affixed resource name.
apiops CLI version
1.0.0
Identified by reading the source at commit 420b993, not from a failed run. See the note below.
Environment details
I have not reproduced this against a live instance. This was found while reviewing how v1.0.0 handles cross-instance ids, after hitting the equivalent gap in the archived Azure/apiops (where scope is relativized on extract and ownerId is not, for which I opened Azure/apiops#877 before noticing that repository is being archived).
Happy to confirm against a real pair of instances if that would help, and happy to send a PR if you agree with the shape of the fix.
CI/CD environment
N/A (running locally)
Is this bug blocking you?
No
Command that triggered the bug
apiops publishExpected behavior
normalizeSubscriptionScopeinsrc/services/resource-publisher.tsdocuments the intent clearly:A subscription extracted from one instance and published to another should not need manual edits.
properties.ownerIdis a service-scoped ARM id in exactly the same wayproperties.scopeis, so I would expect it to be normalized the same way.Microsoft documents
ownerIdas relative:SubscriptionContract.ownerId
Actual behavior
scopeis normalized on publish,ownerIdis not, so a subscription published to a different instance than it was extracted from carries the source instance'sownerId.Extraction stores the ARM response as-is, which is deliberate:
So the artifact on disk holds both ids absolute:
{ "properties": { "scope": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{src-svc}/products/starter", "ownerId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{src-svc}/users/1" } }On publish,
resource-publisher.ts:426callsnormalizeSubscriptionScope, which rewritesscopeto/products/starter. Nothing does the equivalent forownerId, so it is sent unchanged, still naming{src-svc}.ownerIddoes not appear anywhere insrc/. The only occurrence in the repository is a test fixture,tests/unit/services/resource-publisher.test.ts:1309, which sets it absolute alongside an absolutescope.Suggested fix
Extend
normalizeSubscriptionScopeto coverownerId, or add a sibling that applies the same service-prefix strip to it. The existingserviceScopeMatchregex already isolates the trailing APIM-relative segment, so the transform is the same shape:.../service/{svc}/users/1becomes/users/1.Worth noting
ownerIddiffers fromscopein one respect:envMappingaffixes should almost certainly not apply to it, since a user id is not an affixed resource name.apiops CLI version
1.0.0
Identified by reading the source at commit
420b993, not from a failed run. See the note below.Environment details
I have not reproduced this against a live instance. This was found while reviewing how v1.0.0 handles cross-instance ids, after hitting the equivalent gap in the archived Azure/apiops (where
scopeis relativized on extract andownerIdis not, for which I opened Azure/apiops#877 before noticing that repository is being archived).Happy to confirm against a real pair of instances if that would help, and happy to send a PR if you agree with the shape of the fix.
CI/CD environment
N/A (running locally)
Is this bug blocking you?
No